baremetal: build userland/ programs using baremetal path property instead of symlinks

Otherwise I'll go crazy with symlink action.
This commit is contained in:
Ciro Santilli 六四事件 法轮功
2019-05-24 00:00:00 +00:00
parent edfbe9f0d7
commit 05aa5c7c79
49 changed files with 372 additions and 271 deletions

View File

@@ -1206,35 +1206,6 @@ The terminal prints:
hello hello
.... ....
Now let's run link:baremetal/arch/aarch64/add.S[]:
....
./run --arch aarch64 --baremetal baremetal/arch/aarch64/add.S
....
This time, the terminal does not print anything, which indicates success.
If you look into the source, you will see that we just have an assertion there.
You can see a sample assertion fail in link:baremetal/interactive/assert_fail.c[]:
....
./run --arch aarch64 --baremetal baremetal/interactive/assert_fail.c
....
and the terminal contains:
....
lkmc_test_fail
error: simulation error detected by parsing logs
....
and the exit status of our script is 1:
....
echo $?
....
To modify a baremetal program, simply edit the file, e.g. To modify a baremetal program, simply edit the file, e.g.
.... ....
@@ -1252,17 +1223,44 @@ and rebuild:
`./build-baremetal` uses crosstool-NG, and so it must be preceded by link:build-crosstool-ng[], which `./build qemu-baremetal` also calls. `./build-baremetal` uses crosstool-NG, and so it must be preceded by link:build-crosstool-ng[], which `./build qemu-baremetal` also calls.
Alternatively, for the sake of tab completion, we also accept relative paths inside `baremetal/`, for example the following also work:
Now let's run link:baremetal/arch/aarch64/add.S[]:
.... ....
./run --arch aarch64 --baremetal baremetal/c/hello.c
./run --arch aarch64 --baremetal baremetal/arch/aarch64/add.S ./run --arch aarch64 --baremetal baremetal/arch/aarch64/add.S
.... ....
Absolute paths however are used as is and must point to the actual executable: This time, the terminal does not print anything, which indicates success: if you look into the source, you will see that we just have an assertion there.
You can see a sample assertion fail in link:baremetal/interactive/assert_fail.c[]:
.... ....
./run --arch aarch64 --baremetal "$(./getvar --arch aarch64 baremetal_build_dir)/hello.elf" ./run --arch aarch64 --baremetal userland/c/assert_fail.c
....
and the terminal contains:
....
lkmc_test_fail
error: simulation error detected by parsing logs
....
and the exit status of our script is 1:
....
echo $?
....
As suggested by the above example, several of the <<userland-content,userland examples>> can also be run in baremetal! This is largely due to the <<about-the-baremetal-setup,awesomeness of Newlib>>.
The examples that work include most <<c,C examples>> that don't rely on complicated syscalls such as threads, and almost all the <<userland-assembly>> examples.
The exact list of userland programs that work in baremetal is specified in <<path-properties>> with the `baremetal` property.
You can run all the baremetal examples in one go and check that all assertions passed with:
....
./test-baremetal --arch aarch64
.... ....
To use gem5 instead of QEMU do: To use gem5 instead of QEMU do:
@@ -11554,6 +11552,9 @@ Programs under link:userland/c/[] are examples of link:https://en.wikipedia.org/
* Standard library * Standard library
** assert.h ** assert.h
*** link:userland/c/assert_fail.c[] *** link:userland/c/assert_fail.c[]
** `stdlib.h`
*** Exit related
**** link:userland/c/abort.c[]
Userland assembly content is located at: <<userland-assembly>>. It was split from this section basically becase we were hitting the HTML `h6` limit, stupid web :-) Userland assembly content is located at: <<userland-assembly>>. It was split from this section basically becase we were hitting the HTML `h6` limit, stupid web :-)
@@ -11677,7 +11678,7 @@ Other infrastructure sanity checks that you might want to look into include:
* link:userland/arch/empty.S[] * link:userland/arch/empty.S[]
* `LKMC_FAIL` tests * `LKMC_FAIL` tests
** link:userland/arch/fail.S[] ** link:userland/arch/lkmc_assert_fail.S[]
* `LKMC_ASSERT_EQ` tests * `LKMC_ASSERT_EQ` tests
** link:userland/arch/x86_64/lkmc_assert_eq_fail.S[] ** link:userland/arch/x86_64/lkmc_assert_eq_fail.S[]
** link:userland/arch/arm/lkmc_assert_eq_fail.S[] ** link:userland/arch/arm/lkmc_assert_eq_fail.S[]
@@ -13609,8 +13610,8 @@ info threads
shows something like: shows something like:
.... ....
* 1 Thread 1 (CPU#0 [running]) mystart * 1 Thread 1 (CPU#0 [running]) lkmc_start
2 Thread 2 (CPU#1 [halted ]) mystart 2 Thread 2 (CPU#1 [halted ]) lkmc_start
.... ....
To wake up CPU 1 on QEMU, we must use the Power State Coordination Interface (PSCI) which is documented at: link:https://developer.arm.com/docs/den0022/latest/arm-power-state-coordination-interface-platform-design-document[]. To wake up CPU 1 on QEMU, we must use the Power State Coordination Interface (PSCI) which is documented at: link:https://developer.arm.com/docs/den0022/latest/arm-power-state-coordination-interface-platform-design-document[].

View File

@@ -1,5 +1,5 @@
#include <lkmc/m5ops.h> #include <lkmc/m5ops.h>
.global mystart .global lkmc_start
mystart: lkmc_start:
LKMC_M5OPS_EXIT_ASM LKMC_M5OPS_EXIT_ASM

View File

@@ -1,7 +1,7 @@
/* https://github.com/cirosantilli/linux-kernel-module-cheat#semihosting */ /* https://github.com/cirosantilli/linux-kernel-module-cheat#semihosting */
.global mystart .global lkmc_start
mystart: lkmc_start:
mov x1, 0x26 mov x1, 0x26
movk x1, 2, lsl 16 movk x1, 2, lsl 16
ldr x2, =semihost_args ldr x2, =semihost_args

View File

@@ -1,3 +1,3 @@
.global mystart .global lkmc_start
mystart: lkmc_start:
mov r0, #0; mov r1, #0; .inst 0xEE000110 | (0x21 << 16); mov r0, #0; mov r1, #0; .inst 0xEE000110 | (0x21 << 16);

View File

@@ -1,5 +1,5 @@
.global mystart .global lkmc_start
mystart: lkmc_start:
mov r0, #0x18 mov r0, #0x18
ldr r1, =#0x20026 ldr r1, =#0x20026
svc 0x00123456 svc 0x00123456

View File

@@ -1 +0,0 @@
../../lkmc/c/add.c

View File

@@ -1 +0,0 @@
../../lkmc/c/add.py

View File

@@ -1 +0,0 @@
../../lkmc/c/assert_fail.c

View File

@@ -1 +0,0 @@
../../lkmc/c/hello.c

View File

@@ -1 +0,0 @@
../../lkmc/c/infinite_loop.c

View File

@@ -1 +0,0 @@
../../lkmc/c/stderr.c

View File

@@ -1,7 +1,7 @@
#include <lkmc.h> #include <lkmc.h>
.global mystart .global lkmc_start
mystart: lkmc_start:
/* = NEON setup */ /* = NEON setup */
mov x1, #(0x3 << 20) mov x1, #(0x3 << 20)
msr cpacr_el1, x1 msr cpacr_el1, x1
@@ -20,6 +20,7 @@ mystart:
bl on_exit bl on_exit
/* Run main. */ /* Run main. */
mov x0, 0
bl main bl main
/* If main returns, exit. */ /* If main returns, exit. */

View File

@@ -1,7 +1,7 @@
#include <lkmc.h> #include <lkmc.h>
.global mystart .global lkmc_start
mystart: lkmc_start:
/* Prepare the stack for main, mandatory for C code. */ /* Prepare the stack for main, mandatory for C code. */
ldr sp, =stack_top ldr sp, =stack_top
@@ -10,6 +10,7 @@ mystart:
bl on_exit bl on_exit
/* Run main. */ /* Run main. */
mov r0, 0
bl main bl main
/* If main returns, exit. */ /* If main returns, exit. */

View File

@@ -5,6 +5,13 @@
#include <lkmc.h> #include <lkmc.h>
#include <lkmc/m5ops.h> #include <lkmc/m5ops.h>
void lkmc_baremetal_on_exit_callback(int status, void *arg) {
(void)arg;
if (status != 0) {
printf("lkmc_exit_status_%d\n", status);
}
}
enum { enum {
UART_FR_RXFE = 0x10, UART_FR_RXFE = 0x10,
}; };

View File

@@ -1,4 +1,4 @@
ENTRY(mystart) ENTRY(lkmc_start)
SECTIONS SECTIONS
{ {
.text : { .text : {

View File

@@ -1 +0,0 @@
int main(void) { return 0; }

View File

@@ -14,6 +14,7 @@ class Main(common.BuildCliFunction):
description='''\ description='''\
Build the baremetal examples with crosstool-NG. Build the baremetal examples with crosstool-NG.
''', ''',
is_baremetal=True,
supported_archs=common.consts['crosstool_ng_supported_archs'] supported_archs=common.consts['crosstool_ng_supported_archs']
) )
self._add_argument('--ccflags') self._add_argument('--ccflags')
@@ -137,7 +138,10 @@ Build the baremetal examples with crosstool-NG.
def setup_one(self): def setup_one(self):
self.env['targets'] = self.resolve_targets( self.env['targets'] = self.resolve_targets(
self.env['baremetal_source_dir'], [
self.env['baremetal_source_dir'],
self.env['userland_source_dir']
],
self.env['targets'] self.env['targets']
) )

View File

@@ -15,7 +15,7 @@ class Main(common.BuildCliFunction):
kwargs['description'] = '''\ kwargs['description'] = '''\
Build our compiled userland examples. Build our compiled userland examples.
''' '''
super().__init__(*args, **kwargs) super().__init__(*args, is_userland=True, **kwargs)
self._add_argument('--ccflags') self._add_argument('--ccflags')
self._add_argument('--force-rebuild') self._add_argument('--force-rebuild')
self._add_argument('--optimization-level') self._add_argument('--optimization-level')
@@ -109,7 +109,7 @@ Default: build all examples that have their package dependencies met, e.g.:
def setup_one(self): def setup_one(self):
self.env['targets'] = self.resolve_targets( self.env['targets'] = self.resolve_targets(
self.env['userland_source_dir'], [self.env['userland_source_dir']],
self.env['targets'] self.env['targets']
) )

View File

@@ -162,6 +162,7 @@ class LkmcCliFunction(cli_function.CliFunction):
def __init__( def __init__(
self, self,
*args, *args,
is_baremetal=False,
is_userland=False, is_userland=False,
defaults=None, defaults=None,
supported_archs=None, supported_archs=None,
@@ -175,7 +176,8 @@ class LkmcCliFunction(cli_function.CliFunction):
kwargs['extra_config_params'] = os.path.basename(inspect.getfile(self.__class__)) kwargs['extra_config_params'] = os.path.basename(inspect.getfile(self.__class__))
if defaults is None: if defaults is None:
defaults = {} defaults = {}
self._is_userland = is_userland self.is_baremetal = is_baremetal
self.is_userland = is_userland
self._defaults = defaults self._defaults = defaults
self._is_common = True self._is_common = True
self._common_args = set() self._common_args = set()
@@ -1035,8 +1037,12 @@ lunch aosp_{}-eng
self._common_args.add(key) self._common_args.add(key)
super().add_argument(*args, **kwargs) super().add_argument(*args, **kwargs)
def assert_is_subpath(self, subpath, parent): def assert_is_subpath(self, subpath, parents):
if not self.is_subpath(subpath, parent): is_subpath = False
for parent in parents:
if self.is_subpath(subpath, parent):
is_subpath = True
if not is_subpath:
raise Exception( raise Exception(
'Can only accept targets inside {}, given: {}'.format( 'Can only accept targets inside {}, given: {}'.format(
parent, parent,
@@ -1204,7 +1210,7 @@ lunch aosp_{}-eng
continue continue
else: else:
raise Exception('native emulator only supported in if target arch == host arch') raise Exception('native emulator only supported in if target arch == host arch')
if env['userland'] is None and not self._is_userland: if env['userland'] is None and not self.is_userland:
if real_all_emulators: if real_all_emulators:
continue continue
else: else:
@@ -1314,7 +1320,7 @@ lunch aosp_{}-eng
def resolve_executable( def resolve_executable(
self, self,
in_path, in_path,
magic_in_dir, magic_in_dirs,
magic_out_dir, magic_out_dir,
executable_ext executable_ext
): ):
@@ -1331,35 +1337,47 @@ lunch aosp_{}-eng
''' '''
if not self.env['dry_run'] and not os.path.exists(in_path): if not self.env['dry_run'] and not os.path.exists(in_path):
raise Exception('Input path does not exist: ' + in_path) raise Exception('Input path does not exist: ' + in_path)
if self.is_subpath(in_path, magic_in_dir): if len(magic_in_dirs) > 1:
# Abspath needed to remove the trailing `/.` which makes e.g. rmrf fail. relative_subpath = self.env['root_dir']
out = os.path.abspath(os.path.join(
magic_out_dir,
os.path.relpath(
os.path.splitext(in_path)[0],
magic_in_dir
)
))
if os.path.isfile(in_path):
out += executable_ext
return out
else: else:
return in_path relative_subpath = magic_in_dirs[0]
for magic_in_dir in magic_in_dirs:
if self.is_subpath(in_path, magic_in_dir):
# Abspath needed to remove the trailing `/.` which makes e.g. rmrf fail.
out = os.path.abspath(os.path.join(
magic_out_dir,
os.path.relpath(
os.path.splitext(in_path)[0],
relative_subpath
)
))
if os.path.isfile(in_path):
out += executable_ext
return out
return in_path
def resolve_targets(self, source_dir, targets): def resolve_targets(self, source_dirs, targets):
'''
Resolve userland or baremetal CLI provided targets to final paths.
Notably converts the toplevel directory into all source directories needed.
'''
if not targets: if not targets:
targets = [source_dir] targets = source_dirs.copy()
new_targets = [] new_targets = []
for target in targets: for target in targets:
target = self.toplevel_to_source_dir(target, source_dir) for resolved_target in self.toplevel_to_source_dirs(target, source_dirs):
self.assert_is_subpath(target, source_dir) self.assert_is_subpath(resolved_target, source_dirs)
new_targets.append(target) new_targets.append(resolved_target)
return new_targets return new_targets
def resolve_baremetal_executable(self, path): def resolve_baremetal_executable(self, path):
return self.resolve_executable( return self.resolve_executable(
path, path,
self.env['baremetal_source_dir'], [
self.env['baremetal_source_dir'],
self.env['userland_source_dir']
],
self.env['baremetal_build_dir'], self.env['baremetal_build_dir'],
self.env['baremetal_executable_ext'], self.env['baremetal_executable_ext'],
) )
@@ -1367,7 +1385,7 @@ lunch aosp_{}-eng
def resolve_userland_executable(self, path): def resolve_userland_executable(self, path):
return self.resolve_executable( return self.resolve_executable(
path, path,
self.env['userland_source_dir'], [self.env['userland_source_dir']],
self.env['userland_build_dir'], self.env['userland_build_dir'],
self.env['userland_executable_ext'], self.env['userland_executable_ext'],
) )
@@ -1386,12 +1404,12 @@ lunch aosp_{}-eng
''' '''
pass pass
def toplevel_to_source_dir(self, path, source_dir): def toplevel_to_source_dirs(self, path, source_dirs):
path = os.path.abspath(path) path = os.path.abspath(path)
if path == self.env['root_dir']: if path == self.env['root_dir']:
return source_dir return source_dirs
else: else:
return path return [path]
def timed_main(self): def timed_main(self):
''' '''
@@ -1491,13 +1509,21 @@ https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-debug-build
dirpath_relative_root, dirpath_relative_root,
in_basename in_basename
)) ))
if my_path_properties.should_be_built(self.env, link): if my_path_properties.should_be_built(
self.env,
link,
is_baremetal=self.is_baremetal,
is_userland=self.is_userland
):
if extra_objs is None: if extra_objs is None:
extra_objs= [] extra_objs= []
if link: if link:
if my_path_properties['extra_objs_lkmc_common']: if self.is_baremetal or my_path_properties['extra_objs_lkmc_common']:
extra_objs.extend(extra_objs_lkmc_common) extra_objs.extend(extra_objs_lkmc_common)
if my_path_properties['extra_objs_baremetal_bootloader']: if (
self.is_baremetal and
not my_path_properties['extra_objs_disable_baremetal_bootloader']
):
extra_objs.extend(extra_objs_baremetal_bootloader) extra_objs.extend(extra_objs_baremetal_bootloader)
if self.need_rebuild([in_path] + extra_objs + extra_deps, out_path): if self.need_rebuild([in_path] + extra_objs + extra_deps, out_path):
cc_flags.extend(my_path_properties['cc_flags']) cc_flags.extend(my_path_properties['cc_flags'])
@@ -1542,7 +1568,7 @@ https://github.com/cirosantilli/linux-kernel-module-cheat#gem5-debug-build
'cc_flags_after': [], 'cc_flags_after': [],
}, },
} }
package_key = dirpath_relative_root_components[1] package_key = dirpath_relative_root_components[2]
if package_key in packages: if package_key in packages:
package = packages[package_key] package = packages[package_key]
else: else:

9
lkmc.c
View File

@@ -23,7 +23,7 @@ LKMC_ASSERT_EQ_DEFINE(64)
void lkmc_assert_fail(uint32_t line) { void lkmc_assert_fail(uint32_t line) {
printf("error: assertion failed at line: %" PRIu32 "\n", line); printf("error: assertion failed at line: %" PRIu32 "\n", line);
exit(1); abort();
} }
void lkmc_assert_memcmp( void lkmc_assert_memcmp(
@@ -56,13 +56,6 @@ void lkmc_assert_memcmp(
} }
} }
void lkmc_baremetal_on_exit_callback(int status, void *arg) {
(void)arg;
if (status != 0) {
printf("lkmc_exit_status_%d\n", status);
}
}
#if defined(__aarch64__) #if defined(__aarch64__)
#define LKMC_SYSREG_READ_WRITE(type, name) \ #define LKMC_SYSREG_READ_WRITE(type, name) \
type LKMC_CONCAT(LKMC_CONCAT(LKMC_SYSREG_SYMBOL_PREFIX, name), _read(void)) { \ type LKMC_CONCAT(LKMC_CONCAT(LKMC_SYSREG_SYMBOL_PREFIX, name), _read(void)) { \

1
lkmc.h
View File

@@ -20,7 +20,6 @@ LKMC_ASSERT_EQ_DECLARE(32);
LKMC_ASSERT_EQ_DECLARE(64); LKMC_ASSERT_EQ_DECLARE(64);
void lkmc_assert_fail(uint32_t line); void lkmc_assert_fail(uint32_t line);
void lkmc_assert_memcmp(const void *s1, const void *s2, size_t n, uint32_t line); void lkmc_assert_memcmp(const void *s1, const void *s2, size_t n, uint32_t line);
void lkmc_baremetal_on_exit_callback(int status, void *arg);
#endif #endif
/* Assert that the given branch instruction is taken. */ /* Assert that the given branch instruction is taken. */

View File

@@ -1,13 +0,0 @@
#include <assert.h>
int main(void) {
int i, j, k;
i = 1;
/* test-gdb-op1 */
j = 2;
/* test-gdb-op2 */
k = i + j;
/* test-gdb-result */
if (k != 3)
assert(0);
}

View File

@@ -1,9 +0,0 @@
def test(self):
self.sendline('tbreak main')
self.sendline('continue')
self.continue_to('op1')
assert self.get_int('i') == 1
self.continue_to('op2')
assert self.get_int('j') == 2
self.continue_to('result')
assert self.get_int('k') == 3

View File

@@ -1,18 +0,0 @@
/* Let's see what happens when an assert fails.
*
* Outcome on Ubuntu 19.04 shows the failure line:
*
* assert_fail.out: /path/to/linux-kernel-module-cheat/userland/c/assert_fail.c:15: main: Assertion `0' failed.
*
* and exit status 134 == 128 + 6, which corresponds to SIGABORT (6).
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
assert(0);
puts("here");
return EXIT_SUCCESS;
}

View File

@@ -1,21 +0,0 @@
/* Get on character from stdin, and then print it back out.
*
* Same as getc(stdin).
*
* You have to press enter for the character to go through:
* https://stackoverflow.com/questions/1798511/how-to-avoid-pressing-enter-with-getchar
*
* Used at:
* https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1/53937376#53937376
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char c;
printf("enter a character: ");
c = getchar();
printf("you entered: %c\n", c);
return EXIT_SUCCESS;
}

View File

@@ -1,9 +0,0 @@
/* Print hello to stdout ;-) */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
puts("hello");
return EXIT_SUCCESS;
}

View File

@@ -1,29 +0,0 @@
/* 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++;
}
}
}

View File

@@ -1,7 +0,0 @@
/* Print hello to stderr. */
#include <stdio.h>
int main(void) {
fputs("hello\n", stderr);
}

View File

@@ -1,14 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import signal
from shell_helpers import LF from shell_helpers import LF
class PathProperties: class PathProperties:
default_c_std = 'c11' default_c_std = 'c11'
default_cxx_std = 'c++17' default_cxx_std = 'c++17'
# All new properties must be listed here or else you get an error.
default_properties = { default_properties = {
'allowed_archs': None, 'allowed_archs': None,
# Examples that can be built in baremetal.
'baremetal': False,
'c_std': default_c_std, 'c_std': default_c_std,
'cc_flags': [ 'cc_flags': [
'-Wall', LF, '-Wall', LF,
@@ -29,11 +33,14 @@ class PathProperties:
'cc_flags_after': ['-lm', LF], 'cc_flags_after': ['-lm', LF],
'cc_pedantic': True, 'cc_pedantic': True,
'cxx_std': default_cxx_std, 'cxx_std': default_cxx_std,
'disrupts_system': False,
# Expected program exit status. When signals are raised, this refers # Expected program exit status. When signals are raised, this refers
# to the native exit status. as reported by Bash #?. # to the native exit status. as reported by Bash #?.
'exit_status': 0, 'exit_status': 0,
'extra_objs': [], 'extra_objs': [],
'extra_objs_baremetal_bootloader': False, # Explicitly don't add the baremetal bootloader object which normally gets automatically
# added to baremetal examples.
'extra_objs_disable_baremetal_bootloader': False,
# We should get rid of this if we ever properly implement dependency graphs. # We should get rid of this if we ever properly implement dependency graphs.
'extra_objs_lkmc_common': False, 'extra_objs_lkmc_common': False,
'interactive': False, 'interactive': False,
@@ -45,10 +52,7 @@ class PathProperties:
# it only generates intermediate object files. Therefore it # it only generates intermediate object files. Therefore it
# should not be run while testing. # should not be run while testing.
'no_executable': False, 'no_executable': False,
# the test receives a signal. We skip those tests for now, 'receives_signal': None,
# on userland because we are lazy to figure out the exact semantics
# of how Python + QEMU + gem5 determine the exit status of signals.
'receives_signal': False,
# The script requires a non-trivial argument to be passed to run properly. # The script requires a non-trivial argument to be passed to run properly.
'requires_argument': False, 'requires_argument': False,
'requires_dynamic_library': False, 'requires_dynamic_library': False,
@@ -68,6 +72,8 @@ class PathProperties:
# Aruments added automatically to run when running tests, # Aruments added automatically to run when running tests,
# but not on manual running. # but not on manual running.
'test_run_args': {}, 'test_run_args': {},
# Examples that can be built in userland.
'userland': False,
} }
''' '''
@@ -93,7 +99,13 @@ class PathProperties:
def set_path_components(self, path_components): def set_path_components(self, path_components):
self.path_components = path_components self.path_components = path_components
def should_be_built(self, env, link=False): def should_be_built(
self,
env,
link=False,
is_baremetal=False,
is_userland=False,
):
if len(self.path_components) > 1 and \ if len(self.path_components) > 1 and \
self.path_components[1] == 'libs' and \ self.path_components[1] == 'libs' and \
not env['package_all'] and \ not env['package_all'] and \
@@ -105,14 +117,22 @@ class PathProperties:
self['allowed_archs'] is None or self['allowed_archs'] is None or
env['arch'] in self['allowed_archs'] env['arch'] in self['allowed_archs']
) and \ ) and \
(
(is_userland and self['userland'] ) or
(is_baremetal and self['baremetal'])
) and \
not ( not (
link and link and
self['no_executable'] self['no_executable']
) )
def should_be_tested(self, env): def should_be_tested(self, env, is_baremetal=False, is_userland=False):
return ( return (
self.should_be_built(env) and self.should_be_built(
env,
is_baremetal=is_baremetal,
is_userland=is_userland
) and
not self['interactive'] and not self['interactive'] and
not self['more_than_1s'] and not self['more_than_1s'] and
not self['no_executable'] and not self['no_executable'] and
@@ -203,6 +223,7 @@ gnu_extension_properties = {
'cxx_std': 'gnu++17' 'cxx_std': 'gnu++17'
} }
freestanding_properties = { freestanding_properties = {
'baremetal': False,
'cc_flags': [ 'cc_flags': [
'-ffreestanding', LF, '-ffreestanding', LF,
'-nostdlib', LF, '-nostdlib', LF,
@@ -216,8 +237,7 @@ path_properties_tuples = (
{ {
'baremetal': ( 'baremetal': (
{ {
'extra_objs_baremetal_bootloader': True, 'baremetal': True,
'extra_objs_lkmc_common': True,
}, },
{ {
'arch': ( 'arch': (
@@ -229,7 +249,7 @@ path_properties_tuples = (
'gem5_assert.S': {'requires_m5ops': True}, 'gem5_assert.S': {'requires_m5ops': True},
'multicore.S': {'test_run_args': {'cpus': 2}}, 'multicore.S': {'test_run_args': {'cpus': 2}},
'no_bootloader': ( 'no_bootloader': (
{'extra_objs_baremetal_bootloader': False}, {'extra_objs_disable_baremetal_bootloader': True},
{ {
'gem5_exit.S': {'requires_m5ops': True}, 'gem5_exit.S': {'requires_m5ops': True},
'semihost_exit.S': {'requires_semihosting': True}, 'semihost_exit.S': {'requires_semihosting': True},
@@ -245,7 +265,7 @@ path_properties_tuples = (
{ {
'multicore.S': {'test_run_args': {'cpus': 2}}, 'multicore.S': {'test_run_args': {'cpus': 2}},
'no_bootloader': ( 'no_bootloader': (
{'extra_objs_baremetal_bootloader': False}, {'extra_objs_disable_baremetal_bootloader': True},
{ {
'gem5_exit.S': {'requires_m5ops': True}, 'gem5_exit.S': {'requires_m5ops': True},
'semihost_exit.S': {'requires_semihosting': True}, 'semihost_exit.S': {'requires_semihosting': True},
@@ -257,32 +277,22 @@ path_properties_tuples = (
) )
} }
), ),
'c': (
{},
{
'assert_fail.c': {'exit_status': 134},
'infinite_loop.c': {'more_than_1s': True},
}
),
'exit1.c': {'exit_status': 1},
'lib': {'no_executable': True}, 'lib': {'no_executable': True},
'getchar.c': {'interactive': True}, 'getchar.c': {'interactive': True},
'return1.c': {'exit_status': 1},
'return2.c': {'exit_status': 2},
} }
), ),
'lkmc.c': {
'baremetal': True,
'userland': True,
},
'userland': ( 'userland': (
{ {
'cc_flags': [ 'userland': True,
'-fopenmp', LF,
],
'cc_flags_after': [
'-pthread', LF,
],
}, },
{ {
'arch': ( 'arch': (
{ {
'baremetal': True,
'extra_objs_lkmc_common': True, 'extra_objs_lkmc_common': True,
}, },
{ {
@@ -316,11 +326,10 @@ path_properties_tuples = (
}, },
), ),
'freestanding': freestanding_properties, 'freestanding': freestanding_properties,
'lkmc_assert_eq_fail.S': {'exit_status': 1}, 'lkmc_assert_eq_fail.S': {'receives_signal': signal.Signals.SIGABRT},
'lkmc_assert_memcmp_fail.S': {'exit_status': 1}, 'lkmc_assert_memcmp_fail.S': {'receives_signal': signal.Signals.SIGABRT},
'udf.S': { 'udf.S': {
'exit_status': 132, 'receives_signal': signal.Signals.SIGILL
'receives_signal': True
}, },
} }
), ),
@@ -335,15 +344,16 @@ path_properties_tuples = (
}, },
), ),
'freestanding': freestanding_properties, 'freestanding': freestanding_properties,
'lkmc_assert_eq_fail.S': {'exit_status': 1}, 'lkmc_assert_eq_fail.S': {'receives_signal': signal.Signals.SIGABRT},
'lkmc_assert_memcmp_fail.S': {'exit_status': 1}, 'lkmc_assert_memcmp_fail.S': {'receives_signal': signal.Signals.SIGABRT},
'udf.S': { 'udf.S': {
'exit_status': 132, 'receives_signal': signal.Signals.SIGILL
'receives_signal': True
}, },
} }
), ),
'fail.S': {'exit_status': 1}, 'lkmc_assert_fail.S': {
'receives_signal': signal.Signals.SIGABRT,
},
'x86_64': ( 'x86_64': (
{'allowed_archs': {'x86_64'}}, {'allowed_archs': {'x86_64'}},
{ {
@@ -353,31 +363,44 @@ path_properties_tuples = (
{ {
'freestanding': freestanding_properties, 'freestanding': freestanding_properties,
'ring0.c': { 'ring0.c': {
'exit_status': 139, 'receives_signal': signal.Signals.SIGSEGV
'receives_signal': True
} }
} }
), ),
'freestanding': freestanding_properties, 'freestanding': freestanding_properties,
'lkmc_assert_eq_fail.S': {'exit_status': 1}, 'lkmc_assert_eq_fail.S': {'receives_signal': signal.Signals.SIGABRT},
'lkmc_assert_memcmp_fail.S': {'exit_status': 1}, 'lkmc_assert_memcmp_fail.S': {'receives_signal': signal.Signals.SIGABRT},
} }
), ),
} }
), ),
'c': ( 'c': (
{},
{ {
'assert_fail.c': { 'baremetal': True,
'exit_status': 134, },
'receives_signal': True, {
'abort.c': {
'receives_signal': signal.Signals.SIGABRT,
}, },
'assert_fail.c': {
'receives_signal': signal.Signals.SIGABRT,
},
'exit1.c': {'exit_status': 1},
'exit2.c': {'exit_status': 2},
'false.c': {'exit_status': 1}, 'false.c': {'exit_status': 1},
'getchar.c': {'interactive': True}, 'getchar.c': {'interactive': True},
'infinite_loop.c': {'more_than_1s': True}, 'infinite_loop.c': {'more_than_1s': True},
'out_of_memory.c': {'disrupts_system': True},
'return1.c': {'exit_status': 1},
'return2.c': {'exit_status': 2},
}
),
'gcc': (
gnu_extension_properties,
{
'openmp.c': {'cc_flags': ['-fopenmp', LF]},
} }
), ),
'gcc': gnu_extension_properties,
'kernel_modules': {**gnu_extension_properties, **{'requires_kernel_modules': True}}, 'kernel_modules': {**gnu_extension_properties, **{'requires_kernel_modules': True}},
'libs': ( 'libs': (
{'requires_dynamic_library': True}, {'requires_dynamic_library': True},
@@ -398,8 +421,9 @@ path_properties_tuples = (
'proc_events.c': {'requires_sudo': True}, 'proc_events.c': {'requires_sudo': True},
'sched_getaffinity.c': {'requires_syscall_getcpu': True}, 'sched_getaffinity.c': {'requires_syscall_getcpu': True},
'sched_getaffinity_threads.c': { 'sched_getaffinity_threads.c': {
'requires_syscall_getcpu': True, 'cc_flags_after': ['-pthread', LF],
'more_than_1s': True, 'more_than_1s': True,
'requires_syscall_getcpu': True,
}, },
'time_boot.c': {'requires_sudo': True}, 'time_boot.c': {'requires_sudo': True},
'virt_to_phys_user.c': {'requires_argument': True}, 'virt_to_phys_user.c': {'requires_argument': True},

View File

@@ -11,6 +11,7 @@ import thread_pool
class Main(common.TestCliFunction): class Main(common.TestCliFunction):
def __init__(self): def __init__(self):
super().__init__( super().__init__(
is_baremetal=True,
supported_archs=common.consts['crosstool_ng_supported_archs'], supported_archs=common.consts['crosstool_ng_supported_archs'],
) )
self.add_argument( self.add_argument(
@@ -23,7 +24,10 @@ If given, run only the given tests. Otherwise, run all tests.
def setup_one(self): def setup_one(self):
self.env['tests'] = self.resolve_targets( self.env['tests'] = self.resolve_targets(
self.env['baremetal_source_dir'], [
self.env['baremetal_source_dir'],
self.env['userland_source_dir']
],
self.env['tests'] self.env['tests']
) )
@@ -45,7 +49,7 @@ If given, run only the given tests. Otherwise, run all tests.
if os.path.splitext(in_filename)[1] in (self.env['c_ext'], self.env['asm_ext']): if os.path.splitext(in_filename)[1] in (self.env['c_ext'], self.env['asm_ext']):
path_relative_root = os.path.join(dirpath_relative_root, in_filename) path_relative_root = os.path.join(dirpath_relative_root, in_filename)
my_path_properties = path_properties.get(path_relative_root) my_path_properties = path_properties.get(path_relative_root)
if my_path_properties.should_be_tested(self.env): if my_path_properties.should_be_tested(self.env, is_baremetal=True):
cur_run_args = run_args.copy() cur_run_args = run_args.copy()
cur_run_args.update({ cur_run_args.update({
'baremetal': os.path.relpath(os.path.join(path_abs, in_filename), os.getcwd()), 'baremetal': os.path.relpath(os.path.join(path_abs, in_filename), os.getcwd()),

View File

@@ -28,7 +28,7 @@ If given, run only the given tests. Otherwise, run all tests.
def setup_one(self): def setup_one(self):
self.env['tests'] = self.resolve_targets( self.env['tests'] = self.resolve_targets(
self.env['userland_source_dir'], [self.env['userland_source_dir']],
self.env['tests'] self.env['tests']
) )
@@ -53,7 +53,7 @@ If given, run only the given tests. Otherwise, run all tests.
if os.path.splitext(in_filename)[1] in self.env['build_in_exts']: if os.path.splitext(in_filename)[1] in self.env['build_in_exts']:
path_relative_root = os.path.join(dirpath_relative_root, in_filename) path_relative_root = os.path.join(dirpath_relative_root, in_filename)
my_path_properties = path_properties.get(path_relative_root) my_path_properties = path_properties.get(path_relative_root)
if my_path_properties.should_be_tested(self.env): if my_path_properties.should_be_tested(self.env, is_userland=True):
cur_run_args = run_args.copy() cur_run_args = run_args.copy()
cur_run_args.update({ cur_run_args.update({
'userland': os.path.relpath(os.path.join(path_abs, in_filename), os.getcwd()), 'userland': os.path.relpath(os.path.join(path_abs, in_filename), os.getcwd()),
@@ -65,8 +65,9 @@ If given, run only the given tests. Otherwise, run all tests.
'run_obj': lkmc.import_path.import_path_main('run'), 'run_obj': lkmc.import_path.import_path_main('run'),
'test_id': path_relative_root, 'test_id': path_relative_root,
} }
if my_path_properties['receives_signal']: signal = my_path_properties['receives_signal']
run_test_args['expected_exit_status'] = 128 - my_path_properties['exit_status'] if signal is not None:
run_test_args['expected_exit_status'] = -signal.value
my_thread_pool.submit(run_test_args) my_thread_pool.submit(run_test_args)
return self._handle_thread_pool_errors(my_thread_pool) return self._handle_thread_pool_errors(my_thread_pool)

View File

@@ -18,6 +18,12 @@ LKMC_PROLOGUE
/* Now with a memory stored value. */ /* Now with a memory stored value. */
.data .data
/* TODO why is this align required, and why only the in the baremetal toolchain?
* Otherwise at edfbe9f0d7e9cc40cffd1c68c7c7c30a47fda2a8 + 1 was failing with:
* /path/to/linux-kernel-module-cheat/userland/arch/aarch64/fadd_scalar.S:28:(.text+0x3c): relocation truncated to fit: R_AARCH64_LD_PREL_LO19 against `.data'
* /path/to/linux-kernel-module-cheat/userland/arch/aarch64/fadd_scalar.S:28: warning: One possible cause of this error is that the symbol is being referenced in the indicated code as if it had a larger alignment than was declared where it was defined.
*/
.align 4
my_double_0: my_double_0:
.double 1.5 .double 1.5
my_double_1: my_double_1:

View File

@@ -74,5 +74,6 @@ pc_relative_ldr:
LKMC_ASSERT_EQ(x0, =0x123456789ABCDEF0) LKMC_ASSERT_EQ(x0, =0x123456789ABCDEF0)
LKMC_EPILOGUE LKMC_EPILOGUE
.data .data
.align 4
pc_relative_str: pc_relative_str:
.quad 0x0000000000000000 .quad 0x0000000000000000

34
userland/c/abort.c Normal file
View File

@@ -0,0 +1,34 @@
/* # abort
*
* Raise a SIGABRT, an ANSI C signal which by default kills the program.
*
* ....
* man abort
* ....
*
* Bibliography:
*
* * http://stackoverflow.com/questions/397075/what-is-the-difference-between-exit-and-abort
* * http://stackoverflow.com/questions/3676221/when-abort-is-preferred-over-exit
*
* Differences from exit: does not run regular program teardown:
*
* * does not call `atexit` function.
* * does not call C++ destructors
*
* `assert()` exits the program with abort.
*/
#include <stdlib.h>
#include <stdio.h>
void atexit_func() {
puts("atexit");
}
int main(void) {
/* Will not get called. */
atexit(atexit_func);
abort();
return EXIT_SUCCESS;
}

View File

@@ -1 +0,0 @@
../../lkmc/c/add.c

13
userland/c/add.c Normal file
View File

@@ -0,0 +1,13 @@
#include <assert.h>
int main(void) {
int i, j, k;
i = 1;
/* test-gdb-op1 */
j = 2;
/* test-gdb-op2 */
k = i + j;
/* test-gdb-result */
if (k != 3)
assert(0);
}

View File

@@ -1 +0,0 @@
../../lkmc/c/add.py

9
userland/c/add.py Normal file
View File

@@ -0,0 +1,9 @@
def test(self):
self.sendline('tbreak main')
self.sendline('continue')
self.continue_to('op1')
assert self.get_int('i') == 1
self.continue_to('op2')
assert self.get_int('j') == 2
self.continue_to('result')
assert self.get_int('k') == 3

View File

@@ -1 +0,0 @@
../../lkmc/c/assert_fail.c

18
userland/c/assert_fail.c Normal file
View File

@@ -0,0 +1,18 @@
/* Let's see what happens when an assert fails.
*
* Outcome on Ubuntu 19.04 shows the failure line:
*
* assert_fail.out: /path/to/linux-kernel-module-cheat/userland/c/assert_fail.c:15: main: Assertion `0' failed.
*
* and exit status 134 == 128 + 6, which corresponds to SIGABORT (6).
*/
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
int main(void) {
assert(0);
puts("here");
return EXIT_SUCCESS;
}

7
userland/c/exit2.c Normal file
View File

@@ -0,0 +1,7 @@
/* https://github.com/cirosantilli/linux-kernel-module-cheat#magic-failure-string */
#include <stdlib.h>
int main(void) {
exit(2);
}

View File

@@ -1 +0,0 @@
../../lkmc/c/getchar.c

21
userland/c/getchar.c Normal file
View File

@@ -0,0 +1,21 @@
/* Get on character from stdin, and then print it back out.
*
* Same as getc(stdin).
*
* You have to press enter for the character to go through:
* https://stackoverflow.com/questions/1798511/how-to-avoid-pressing-enter-with-getchar
*
* Used at:
* https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1/53937376#53937376
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char c;
printf("enter a character: ");
c = getchar();
printf("you entered: %c\n", c);
return EXIT_SUCCESS;
}

View File

@@ -1 +0,0 @@
../../lkmc/c/hello.c

9
userland/c/hello.c Normal file
View File

@@ -0,0 +1,9 @@
/* Print hello to stdout ;-) */
#include <stdio.h>
#include <stdlib.h>
int main(void) {
puts("hello");
return EXIT_SUCCESS;
}

View File

@@ -1 +0,0 @@
../../lkmc/c/infinite_loop.c

View 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++;
}
}
}

View File

@@ -1,18 +1,18 @@
/* Test out of memory. */ /* Let's see how much memory Linux lets us allocate. */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
int main(void) { int main(void) {
char *ptr = NULL; char *ptr = NULL;
size_t alloc_size = 1; size_t size = 1;
while (1) { while (1) {
ptr = realloc(ptr, alloc_size); printf("0x%zx\n", size);
ptr = realloc(ptr, size);
if (ptr == NULL) { if (ptr == NULL) {
puts("out of memory");
break; break;
} else { } else {
alloc_size <<= 1; size <<= 1;
} }
} }
} }

6
userland/c/return0.c Normal file
View File

@@ -0,0 +1,6 @@
/* false.c is a superset of this, this is mainly a sanity check for
* baremetal where we don't have CLI arguments yet:
* https://github.com/cirosantilli/linux-kernel-module-cheat/issues/67
*/
int main(void) { return 0; }

View File

@@ -1 +0,0 @@
../../lkmc/c/stderr.c

7
userland/c/stderr.c Normal file
View File

@@ -0,0 +1,7 @@
/* Print hello to stderr. */
#include <stdio.h>
int main(void) {
fputs("hello\n", stderr);
}