diff options
Diffstat (limited to 'tests/tcg/multiarch')
21 files changed, 3130 insertions, 0 deletions
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target new file mode 100644 index 000000000..a83efb4a9 --- /dev/null +++ b/tests/tcg/multiarch/Makefile.target @@ -0,0 +1,117 @@ +# -*- Mode: makefile -*- +# +# Multiarch Tests - included from tests/tcg/Makefile.target +# +# These tests are plain C and built without any architecture specific code. +# + +MULTIARCH_SRC=$(SRC_PATH)/tests/tcg/multiarch + +# Set search path for all sources +VPATH += $(MULTIARCH_SRC) +MULTIARCH_SRCS = $(notdir $(wildcard $(MULTIARCH_SRC)/*.c)) +ifneq ($(CONFIG_LINUX),) +VPATH += $(MULTIARCH_SRC)/linux +MULTIARCH_SRCS += $(notdir $(wildcard $(MULTIARCH_SRC)/linux/*.c)) +endif +MULTIARCH_TESTS = $(MULTIARCH_SRCS:.c=) + +# +# The following are any additional rules needed to build things +# + + +float_%: LDFLAGS+=-lm +float_%: float_%.c libs/float_helpers.c + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $< $(MULTIARCH_SRC)/libs/float_helpers.c -o $@ $(LDFLAGS) + +run-float_%: float_% + $(call run-test,$<, $(QEMU) $(QEMU_OPTS) $<,"$< on $(TARGET_NAME)") + $(call conditional-diff-out,$<,$(SRC_PATH)/tests/tcg/$(TARGET_NAME)/$<.ref) + + +testthread: LDFLAGS+=-lpthread + +threadcount: LDFLAGS+=-lpthread + +signals: LDFLAGS+=-lrt -lpthread + +# We define the runner for test-mmap after the individual +# architectures have defined their supported pages sizes. If no +# additional page sizes are defined we only run the default test. + +# default case (host page size) +run-test-mmap: test-mmap + $(call run-test, test-mmap, $(QEMU) $<, \ + "$< (default) on $(TARGET_NAME)") + +# additional page sizes (defined by each architecture adding to EXTRA_RUNS) +run-test-mmap-%: test-mmap + $(call run-test, test-mmap-$*, $(QEMU) -p $* $<,\ + "$< ($* byte pages) on $(TARGET_NAME)") + +ifneq ($(HAVE_GDB_BIN),) +GDB_SCRIPT=$(SRC_PATH)/tests/guest-debug/run-test.py + +run-gdbstub-sha1: sha1 + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(HAVE_GDB_BIN) \ + --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/sha1.py, \ + "basic gdbstub support") + +run-gdbstub-qxfer-auxv-read: sha1 + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(HAVE_GDB_BIN) \ + --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/test-qxfer-auxv-read.py, \ + "basic gdbstub qXfer:auxv:read support") + +run-gdbstub-thread-breakpoint: testthread + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(HAVE_GDB_BIN) \ + --qemu $(QEMU) --qargs "$(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/test-thread-breakpoint.py, \ + "hitting a breakpoint on non-main thread") + +else +run-gdbstub-%: + $(call skip-test, "gdbstub test $*", "need working gdb") +endif +EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \ + run-gdbstub-thread-breakpoint + +# ARM Compatible Semi Hosting Tests +# +# Despite having ARM in the name we actually have several +# architectures that implement it. We gate the tests on the feature +# appearing in config. +# +ifeq ($(CONFIG_ARM_COMPATIBLE_SEMIHOSTING),y) +VPATH += $(MULTIARCH_SRC)/arm-compat-semi + +# Add -I path back to TARGET_NAME for semicall.h +semihosting: CFLAGS+=-I$(SRC_PATH)/tests/tcg/$(TARGET_NAME) + +run-semihosting: semihosting + $(call run-test,$<,$(QEMU) $< 2> $<.err, "$< on $(TARGET_NAME)") + +run-plugin-semihosting-with-%: + $(call run-test, $@, $(QEMU) $(QEMU_OPTS) \ + -plugin $(PLUGIN_LIB)/$(call extract-plugin,$@) \ + $(call strip-plugin,$<) 2> $<.err, \ + "$< on $(TARGET_NAME) with $*") + +semiconsole: CFLAGS+=-I$(SRC_PATH)/tests/tcg/$(TARGET_NAME) + +run-semiconsole: semiconsole + $(call skip-test, $<, "MANUAL ONLY") + +run-plugin-semiconsole-with-%: + $(call skip-test, $<, "MANUAL ONLY") + +TESTS += semihosting semiconsole +endif + +# Update TESTS +TESTS += $(MULTIARCH_TESTS) diff --git a/tests/tcg/multiarch/README b/tests/tcg/multiarch/README new file mode 100644 index 000000000..522c9d2ea --- /dev/null +++ b/tests/tcg/multiarch/README @@ -0,0 +1 @@ +Multi-architecture linux-user tests diff --git a/tests/tcg/multiarch/arm-compat-semi/semiconsole.c b/tests/tcg/multiarch/arm-compat-semi/semiconsole.c new file mode 100644 index 000000000..1d82efc58 --- /dev/null +++ b/tests/tcg/multiarch/arm-compat-semi/semiconsole.c @@ -0,0 +1,29 @@ +/* + * linux-user semihosting console + * + * Copyright (c) 2019 + * Written by Alex Bennée <alex.bennee@linaro.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define SYS_READC 0x07 + +#include <stdio.h> +#include <stdint.h> +#include "semicall.h" + +int main(void) +{ + char c; + + printf("Semihosting Console Test\n"); + printf("hit X to exit:"); + + do { + c = __semi_call(SYS_READC, 0); + printf("got '%c'\n", c); + } while (c != 'X'); + + return 0; +} diff --git a/tests/tcg/multiarch/arm-compat-semi/semihosting.c b/tests/tcg/multiarch/arm-compat-semi/semihosting.c new file mode 100644 index 000000000..8627eee3c --- /dev/null +++ b/tests/tcg/multiarch/arm-compat-semi/semihosting.c @@ -0,0 +1,82 @@ +/* + * linux-user semihosting checks + * + * Copyright (c) 2019 + * Written by Alex Bennée <alex.bennee@linaro.org> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define SYS_WRITE0 0x04 +#define SYS_HEAPINFO 0x16 +#define SYS_REPORTEXC 0x18 + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "semicall.h" + +int main(int argc, char *argv[argc]) +{ +#if UINTPTR_MAX == UINT32_MAX + uintptr_t exit_code = 0x20026; +#else + uintptr_t exit_block[2] = {0x20026, 0}; + uintptr_t exit_code = (uintptr_t) &exit_block; +#endif + struct { + void *heap_base; + void *heap_limit; + void *stack_base; + void *stack_limit; + } info; + void *ptr_to_info = (void *) &info; + + __semi_call(SYS_WRITE0, (uintptr_t) "Checking HeapInfo\n"); + + memset(&info, 0, sizeof(info)); + __semi_call(SYS_HEAPINFO, (uintptr_t) &ptr_to_info); + + if (info.heap_base == NULL || info.heap_limit == NULL) { + printf("null heap: %p -> %p\n", info.heap_base, info.heap_limit); + exit(1); + } + + /* Error if heap base is above limit */ + if ((uintptr_t) info.heap_base >= (uintptr_t) info.heap_limit) { + printf("heap base %p >= heap_limit %p\n", + info.heap_base, info.heap_limit); + exit(2); + } + + if (info.stack_base == NULL || info.stack_limit) { + printf("null stack: %p -> %p\n", info.stack_base, info.stack_limit); + exit(3); + } + + /* check our local variables are indeed inside the reported stack */ + if (ptr_to_info > info.stack_base) { + printf("info appears to be above stack: %p > %p\n", ptr_to_info, + info.stack_base); + exit(4); + } else if (ptr_to_info < info.stack_limit) { + printf("info appears to be outside stack: %p < %p\n", ptr_to_info, + info.stack_limit); + exit(5); + } + + if (ptr_to_info > info.heap_base && ptr_to_info < info.heap_limit) { + printf("info appears to be inside the heap: %p in %p:%p\n", + ptr_to_info, info.heap_base, info.heap_limit); + exit(6); + } + + printf("heap: %p -> %p\n", info.heap_base, info.heap_limit); + printf("stack: %p -> %p\n", info.stack_base, info.stack_limit); + + __semi_call(SYS_WRITE0, (uintptr_t) "Passed HeapInfo checks"); + __semi_call(SYS_REPORTEXC, exit_code); + /* if we get here we failed */ + return -1; +} diff --git a/tests/tcg/multiarch/float_convs.c b/tests/tcg/multiarch/float_convs.c new file mode 100644 index 000000000..e9be75c2d --- /dev/null +++ b/tests/tcg/multiarch/float_convs.c @@ -0,0 +1,107 @@ +/* + * Floating Point Convert Single to Various + * + * Copyright (c) 2019 Linaro + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <float.h> +#include <fenv.h> + + +#include "float_helpers.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct { + int flag; + char *desc; +} float_mapping; + +float_mapping round_flags[] = { + { FE_TONEAREST, "to nearest" }, +#ifdef FE_UPWARD + { FE_UPWARD, "upwards" }, +#endif +#ifdef FE_DOWNWARD + { FE_DOWNWARD, "downwards" }, +#endif +#ifdef FE_TOWARDZERO + { FE_TOWARDZERO, "to zero" } +#endif +}; + +static void print_input(float input) +{ + char *in_fmt = fmt_f32(input); + printf("from single: %s\n", in_fmt); + free(in_fmt); +} + +static void convert_single_to_double(float input) +{ + double output; + char *out_fmt, *flag_fmt; + + feclearexcept(FE_ALL_EXCEPT); + + output = input; + + out_fmt = fmt_f64(output); + flag_fmt = fmt_flags(); + printf(" to double: %s (%s)\n", out_fmt, flag_fmt); + free(out_fmt); + free(flag_fmt); +} + +#define xstr(a) str(a) +#define str(a) #a + +#define CONVERT_SINGLE_TO_INT(TYPE, FMT) \ + static void convert_single_to_ ## TYPE(float input) \ + { \ + TYPE ## _t output; \ + char *flag_fmt; \ + const char to[] = "to " xstr(TYPE); \ + feclearexcept(FE_ALL_EXCEPT); \ + output = input; \ + flag_fmt = fmt_flags(); \ + printf("%11s: %" FMT " (%s)\n", to, output, flag_fmt); \ + free(flag_fmt); \ + } + +CONVERT_SINGLE_TO_INT( int32, PRId32) +CONVERT_SINGLE_TO_INT(uint32, PRId32) +CONVERT_SINGLE_TO_INT( int64, PRId64) +CONVERT_SINGLE_TO_INT(uint64, PRId64) + +int main(int argc, char *argv[argc]) +{ + int i, j, nums; + + nums = get_num_f32(); + + for (i = 0; i < ARRAY_SIZE(round_flags); ++i) { + if (fesetround(round_flags[i].flag) != 0) { + printf("### Rounding %s skipped\n", round_flags[i].desc); + continue; + } + printf("### Rounding %s\n", round_flags[i].desc); + for (j = 0; j < nums; j++) { + float input = get_f32(j); + print_input(input); + /* convert_single_to_half(input); */ + convert_single_to_double(input); + convert_single_to_int32(input); + convert_single_to_int64(input); + convert_single_to_uint32(input); + convert_single_to_uint64(input); + } + } + + return 0; +} diff --git a/tests/tcg/multiarch/float_helpers.h b/tests/tcg/multiarch/float_helpers.h new file mode 100644 index 000000000..309f3f4bf --- /dev/null +++ b/tests/tcg/multiarch/float_helpers.h @@ -0,0 +1,43 @@ +/* + * Common Float Helpers + * + * Copyright (c) 2019 Linaro + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <inttypes.h> + +/* Some hosts do not have support for all of these; not required by ISO C. */ +#ifndef FE_OVERFLOW +#define FE_OVERFLOW 0 +#endif +#ifndef FE_UNDERFLOW +#define FE_UNDERFLOW 0 +#endif +#ifndef FE_DIVBYZERO +#define FE_DIVBYZERO 0 +#endif +#ifndef FE_INEXACT +#define FE_INEXACT 0 +#endif +#ifndef FE_INVALID +#define FE_INVALID 0 +#endif + +/* Number of constants in each table */ +int get_num_f16(void); +int get_num_f32(void); +int get_num_f64(void); + +/* Accessor helpers, overflows will automatically wrap */ +uint16_t get_f16(int i); /* use _Float16 when we can */ +float get_f32(int i); +double get_f64(int i); + +/* Return format strings, free after use */ +char * fmt_f16(uint16_t); +char * fmt_f32(float); +char * fmt_f64(double); +/* exception flags */ +char * fmt_flags(void); diff --git a/tests/tcg/multiarch/float_madds.c b/tests/tcg/multiarch/float_madds.c new file mode 100644 index 000000000..e422608cc --- /dev/null +++ b/tests/tcg/multiarch/float_madds.c @@ -0,0 +1,105 @@ +/* + * Fused Multiply Add (Single) + * + * Copyright (c) 2019 Linaro + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <float.h> +#include <fenv.h> + +#include "float_helpers.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct { + int flag; + char *desc; +} float_mapping; + +float_mapping round_flags[] = { + { FE_TONEAREST, "to nearest" }, +#ifdef FE_UPWARD + { FE_UPWARD, "upwards" }, +#endif +#ifdef FE_DOWNWARD + { FE_DOWNWARD, "downwards" }, +#endif +#ifdef FE_TOWARDZERO + { FE_TOWARDZERO, "to zero" } +#endif +}; + + +static void print_inputs(float a, float b, float c) +{ + char *a_fmt, *b_fmt, *c_fmt; + + a_fmt = fmt_f32(a); + b_fmt = fmt_f32(b); + c_fmt = fmt_f32(c); + + printf("op : %s * %s + %s\n", a_fmt, b_fmt, c_fmt); + + free(a_fmt); + free(b_fmt); + free(c_fmt); +} + +static void print_result(float r, int j, int k) +{ + char *r_fmt, *flag_fmt; + + r_fmt = fmt_f32(r); + flag_fmt = fmt_flags(); + + printf("res: %s flags=%s (%d/%d)\n", r_fmt, flag_fmt, j, k); + + free(r_fmt); + free(flag_fmt); +} + +static void do_madds(float a, float b, float c, int j, int k) +{ + float r; + + print_inputs(a, b, c); + + feclearexcept(FE_ALL_EXCEPT); + r = __builtin_fmaf(a, b, c); + + print_result(r, j, k); +} + +int main(int argc, char *argv[argc]) +{ + int i, j, k, nums = get_num_f32(); + float a, b, c; + + for (i = 0; i < ARRAY_SIZE(round_flags); ++i) { + if (fesetround(round_flags[i].flag) != 0) { + printf("### Rounding %s skipped\n", round_flags[i].desc); + continue; + } + printf("### Rounding %s\n", round_flags[i].desc); + for (j = 0; j < nums; j++) { + for (k = 0; k < 3; k++) { + a = get_f32(j + ((k)%3)); + b = get_f32(j + ((k+1)%3)); + c = get_f32(j + ((k+2)%3)); + do_madds(a, b, c, j, k); + } + } + + /* From https://bugs.launchpad.net/qemu/+bug/1841491 */ + printf("# LP184149\n"); + do_madds(0x1.ffffffffffffcp-1022, 0x1.0000000000001p-1, 0x0.0000000000001p-1022, j, 0); + do_madds(0x8p-152, 0x8p-152, 0x8p-152, j+1, 0); + } + + return 0; +} diff --git a/tests/tcg/multiarch/gdbstub/memory.py b/tests/tcg/multiarch/gdbstub/memory.py new file mode 100644 index 000000000..67864ad90 --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/memory.py @@ -0,0 +1,130 @@ +from __future__ import print_function +# +# Test some of the softmmu debug features with the multiarch memory +# test. It is a port of the original vmlinux focused test case but +# using the "memory" test instead. +# +# This is launched via tests/guest-debug/run-test.py +# + +import gdb +import sys + +failcount = 0 + + +def report(cond, msg): + "Report success/fail of test" + if cond: + print("PASS: %s" % (msg)) + else: + print("FAIL: %s" % (msg)) + global failcount + failcount += 1 + + +def check_step(): + "Step an instruction, check it moved." + start_pc = gdb.parse_and_eval('$pc') + gdb.execute("si") + end_pc = gdb.parse_and_eval('$pc') + + return not (start_pc == end_pc) + + +# +# Currently it's hard to create a hbreak with the pure python API and +# manually matching PC to symbol address is a bit flaky thanks to +# function prologues. However internally QEMU's gdbstub treats them +# the same as normal breakpoints so it will do for now. +# +def check_break(sym_name): + "Setup breakpoint, continue and check we stopped." + sym, ok = gdb.lookup_symbol(sym_name) + bp = gdb.Breakpoint(sym_name, gdb.BP_BREAKPOINT) + + gdb.execute("c") + + # hopefully we came back + end_pc = gdb.parse_and_eval('$pc') + report(bp.hit_count == 1, + "break @ %s (%s %d hits)" % (end_pc, sym.value(), bp.hit_count)) + + bp.delete() + + +def do_one_watch(sym, wtype, text): + + wp = gdb.Breakpoint(sym, gdb.BP_WATCHPOINT, wtype) + gdb.execute("c") + report_str = "%s for %s" % (text, sym) + + if wp.hit_count > 0: + report(True, report_str) + wp.delete() + else: + report(False, report_str) + + +def check_watches(sym_name): + "Watch a symbol for any access." + + # Should hit for any read + do_one_watch(sym_name, gdb.WP_ACCESS, "awatch") + + # Again should hit for reads + do_one_watch(sym_name, gdb.WP_READ, "rwatch") + + # Finally when it is written + do_one_watch(sym_name, gdb.WP_WRITE, "watch") + + +def run_test(): + "Run through the tests one by one" + + print("Checking we can step the first few instructions") + step_ok = 0 + for i in range(3): + if check_step(): + step_ok += 1 + + report(step_ok == 3, "single step in boot code") + + # If we get here we have missed some of the other breakpoints. + print("Setup catch-all for _exit") + cbp = gdb.Breakpoint("_exit", gdb.BP_BREAKPOINT) + + check_break("main") + check_watches("test_data[128]") + + report(cbp.hit_count == 0, "didn't reach backstop") + +# +# This runs as the script it sourced (via -x, via run-test.py) +# +try: + inferior = gdb.selected_inferior() + arch = inferior.architecture() + print("ATTACHED: %s" % arch.name()) +except (gdb.error, AttributeError): + print("SKIPPING (not connected)", file=sys.stderr) + exit(0) + +if gdb.parse_and_eval('$pc') == 0: + print("SKIP: PC not set") + exit(0) + +try: + # These are not very useful in scripts + gdb.execute("set pagination off") + + # Run the actual tests + run_test() +except (gdb.error): + print("GDB Exception: %s" % (sys.exc_info()[0])) + failcount += 1 + pass + +# Finally kill the inferior and exit gdb with a count of failures +gdb.execute("kill") +exit(failcount) diff --git a/tests/tcg/multiarch/gdbstub/sha1.py b/tests/tcg/multiarch/gdbstub/sha1.py new file mode 100644 index 000000000..423b720e6 --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/sha1.py @@ -0,0 +1,88 @@ +from __future__ import print_function +# +# A very simple smoke test for debugging the SHA1 userspace test on +# each target. +# +# This is launched via tests/guest-debug/run-test.py +# + +import gdb +import sys + +initial_vlen = 0 +failcount = 0 + +def report(cond, msg): + "Report success/fail of test" + if cond: + print("PASS: %s" % (msg)) + else: + print("FAIL: %s" % (msg)) + global failcount + failcount += 1 + +def check_break(sym_name): + "Setup breakpoint, continue and check we stopped." + sym, ok = gdb.lookup_symbol(sym_name) + bp = gdb.Breakpoint(sym_name) + + gdb.execute("c") + + # hopefully we came back + end_pc = gdb.parse_and_eval('$pc') + report(bp.hit_count == 1, + "break @ %s (%s %d hits)" % (end_pc, sym.value(), bp.hit_count)) + + bp.delete() + +def run_test(): + "Run through the tests one by one" + + check_break("SHA1Init") + + # Check step and inspect values. We do a double next after the + # breakpoint as depending on the version of gdb we may step the + # preamble and not the first actual line of source. + gdb.execute("next") + gdb.execute("next") + val_ctx = gdb.parse_and_eval("context->state[0]") + exp_ctx = 0x67452301 + report(int(val_ctx) == exp_ctx, "context->state[0] == %x" % exp_ctx); + + gdb.execute("next") + val_ctx = gdb.parse_and_eval("context->state[1]") + exp_ctx = 0xEFCDAB89 + report(int(val_ctx) == exp_ctx, "context->state[1] == %x" % exp_ctx); + + # finally check we don't barf inspecting registers + gdb.execute("info registers") + +# +# This runs as the script it sourced (via -x, via run-test.py) +# +try: + inferior = gdb.selected_inferior() + arch = inferior.architecture() + print("ATTACHED: %s" % arch.name()) +except (gdb.error, AttributeError): + print("SKIPPING (not connected)", file=sys.stderr) + exit(0) + +if gdb.parse_and_eval('$pc') == 0: + print("SKIP: PC not set") + exit(0) + +try: + # These are not very useful in scripts + gdb.execute("set pagination off") + gdb.execute("set confirm off") + + # Run the actual tests + run_test() +except (gdb.error): + print ("GDB Exception: %s" % (sys.exc_info()[0])) + failcount += 1 + pass + +print("All tests complete: %d failures" % failcount) +exit(failcount) diff --git a/tests/tcg/multiarch/gdbstub/test-qxfer-auxv-read.py b/tests/tcg/multiarch/gdbstub/test-qxfer-auxv-read.py new file mode 100644 index 000000000..d91e8fdf1 --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/test-qxfer-auxv-read.py @@ -0,0 +1,57 @@ +from __future__ import print_function +# +# Test auxiliary vector is loaded via gdbstub +# +# This is launched via tests/guest-debug/run-test.py +# + +import gdb +import sys + +failcount = 0 + +def report(cond, msg): + "Report success/fail of test" + if cond: + print ("PASS: %s" % (msg)) + else: + print ("FAIL: %s" % (msg)) + global failcount + failcount += 1 + +def run_test(): + "Run through the tests one by one" + + auxv = gdb.execute("info auxv", False, True) + report(isinstance(auxv, str), "Fetched auxv from inferior") + report(auxv.find("sha1"), "Found test binary name in auxv") + +# +# This runs as the script it sourced (via -x, via run-test.py) +# +try: + inferior = gdb.selected_inferior() + arch = inferior.architecture() + print("ATTACHED: %s" % arch.name()) +except (gdb.error, AttributeError): + print("SKIPPING (not connected)", file=sys.stderr) + exit(0) + +if gdb.parse_and_eval('$pc') == 0: + print("SKIP: PC not set") + exit(0) + +try: + # These are not very useful in scripts + gdb.execute("set pagination off") + gdb.execute("set confirm off") + + # Run the actual tests + run_test() +except (gdb.error): + print ("GDB Exception: %s" % (sys.exc_info()[0])) + failcount += 1 + pass + +print("All tests complete: %d failures" % failcount) +exit(failcount) diff --git a/tests/tcg/multiarch/gdbstub/test-thread-breakpoint.py b/tests/tcg/multiarch/gdbstub/test-thread-breakpoint.py new file mode 100644 index 000000000..798d508bc --- /dev/null +++ b/tests/tcg/multiarch/gdbstub/test-thread-breakpoint.py @@ -0,0 +1,60 @@ +from __future__ import print_function +# +# Test auxiliary vector is loaded via gdbstub +# +# This is launched via tests/guest-debug/run-test.py +# + +import gdb +import sys + +failcount = 0 + +def report(cond, msg): + "Report success/fail of test" + if cond: + print ("PASS: %s" % (msg)) + else: + print ("FAIL: %s" % (msg)) + global failcount + failcount += 1 + +def run_test(): + "Run through the tests one by one" + + sym, ok = gdb.lookup_symbol("thread1_func") + gdb.execute("b thread1_func") + gdb.execute("c") + + frame = gdb.selected_frame() + report(str(frame.function()) == "thread1_func", "break @ %s"%frame) + +# +# This runs as the script it sourced (via -x, via run-test.py) +# +try: + inferior = gdb.selected_inferior() + arch = inferior.architecture() + print("ATTACHED: %s" % arch.name()) +except (gdb.error, AttributeError): + print("SKIPPING (not connected)", file=sys.stderr) + exit(0) + +if gdb.parse_and_eval('$pc') == 0: + print("SKIP: PC not set") + exit(0) + +try: + # These are not very useful in scripts + gdb.execute("set pagination off") + gdb.execute("set confirm off") + + # Run the actual tests + run_test() +except (gdb.error): + print ("GDB Exception: %s" % (sys.exc_info()[0])) + failcount += 1 + pass + +print("All tests complete: %d failures" % failcount) +exit(failcount) diff --git a/tests/tcg/multiarch/libs/float_helpers.c b/tests/tcg/multiarch/libs/float_helpers.c new file mode 100644 index 000000000..4e68d2b65 --- /dev/null +++ b/tests/tcg/multiarch/libs/float_helpers.c @@ -0,0 +1,228 @@ +/* + * Common Float Helpers + * + * This contains a series of useful utility routines and a set of + * floating point constants useful for exercising the edge cases in + * floating point tests. + * + * Copyright (c) 2019 Linaro + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* we want additional float type definitions */ +#define __STDC_WANT_IEC_60559_BFP_EXT__ +#define __STDC_WANT_IEC_60559_TYPES_EXT__ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <math.h> +#include <float.h> +#include <fenv.h> + +#include "../float_helpers.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +/* + * Half Precision Numbers + * + * Not yet well standardised so we return a plain uint16_t for now. + */ + +/* no handy defines for these numbers */ +static uint16_t f16_numbers[] = { + 0xffff, /* -NaN / AHP -Max */ + 0xfcff, /* -NaN / AHP */ + 0xfc01, /* -NaN / AHP */ + 0xfc00, /* -Inf */ + 0xfbff, /* -Max */ + 0xc000, /* -2 */ + 0xbc00, /* -1 */ + 0x8001, /* -MIN subnormal */ + 0x8000, /* -0 */ + 0x0000, /* +0 */ + 0x0001, /* MIN subnormal */ + 0x3c00, /* 1 */ + 0x7bff, /* Max */ + 0x7c00, /* Inf */ + 0x7c01, /* NaN / AHP */ + 0x7cff, /* NaN / AHP */ + 0x7fff, /* NaN / AHP +Max*/ +}; + +static const int num_f16 = ARRAY_SIZE(f16_numbers); + +int get_num_f16(void) +{ + return num_f16; +} + +uint16_t get_f16(int i) +{ + return f16_numbers[i % num_f16]; +} + +/* only display as hex */ +char *fmt_16(uint16_t num) +{ + char *fmt; + asprintf(&fmt, "f16(%#04x)", num); + return fmt; +} + +/* + * Single Precision Numbers + */ + +#ifndef SNANF +/* Signaling NaN macros, if supported. */ +# define SNANF (__builtin_nansf ("")) +# define SNAN (__builtin_nans ("")) +# define SNANL (__builtin_nansl ("")) +#endif + +static float f32_numbers[] = { + -SNANF, + -NAN, + -INFINITY, + -FLT_MAX, + -0x1.1874b2p+103, + -0x1.c0bab6p+99, + -0x1.31f75p-40, + -0x1.505444p-66, + -FLT_MIN, + 0.0, + FLT_MIN, + 0x1p-25, + 0x1.ffffe6p-25, /* min positive FP16 subnormal */ + 0x1.ff801ap-15, /* max subnormal FP16 */ + 0x1.00000cp-14, /* min positive normal FP16 */ + 1.0, + 0x1.004p+0, /* smallest float after 1.0 FP16 */ + 2.0, + M_E, M_PI, + 0x1.ffbep+15, + 0x1.ffcp+15, /* max FP16 */ + 0x1.ffc2p+15, + 0x1.ffbfp+16, + 0x1.ffcp+16, /* max AFP */ + 0x1.ffc1p+16, + 0x1.c0bab6p+99, + FLT_MAX, + INFINITY, + NAN, + SNANF +}; + +static const int num_f32 = ARRAY_SIZE(f32_numbers); + +int get_num_f32(void) +{ + return num_f32; +} + +float get_f32(int i) +{ + return f32_numbers[i % num_f32]; +} + +char *fmt_f32(float num) +{ + uint32_t single_as_hex = *(uint32_t *) # + char *fmt; + asprintf(&fmt, "f32(%02.20a:%#010x)", num, single_as_hex); + return fmt; +} + + +/* This allows us to initialise some doubles as pure hex */ +typedef union { + double d; + uint64_t h; +} test_doubles; + +static test_doubles f64_numbers[] = { + {SNAN}, + {-NAN}, + {-INFINITY}, + {-DBL_MAX}, + {-FLT_MAX-1.0}, + {-FLT_MAX}, + {-1.111E+31}, + {-1.111E+30}, /* half prec */ + {-2.0}, {-1.0}, + {-DBL_MIN}, + {-FLT_MIN}, + {0.0}, + {FLT_MIN}, + {2.98023224e-08}, + {5.96046E-8}, /* min positive FP16 subnormal */ + {6.09756E-5}, /* max subnormal FP16 */ + {6.10352E-5}, /* min positive normal FP16 */ + {1.0}, + {1.0009765625}, /* smallest float after 1.0 FP16 */ + {DBL_MIN}, + {1.3789972848607228e-308}, + {1.4914738736681624e-308}, + {1.0}, {2.0}, + {M_E}, {M_PI}, + {65503.0}, + {65504.0}, /* max FP16 */ + {65505.0}, + {131007.0}, + {131008.0}, /* max AFP */ + {131009.0}, + {.h = 0x41dfffffffc00000 }, /* to int = 0x7fffffff */ + {FLT_MAX}, + {FLT_MAX + 1.0}, + {DBL_MAX}, + {INFINITY}, + {NAN}, + {.h = 0x7ff0000000000001}, /* SNAN */ + {SNAN}, +}; + +static const int num_f64 = ARRAY_SIZE(f64_numbers); + +int get_num_f64(void) +{ + return num_f64; +} + +double get_f64(int i) +{ + return f64_numbers[i % num_f64].d; +} + +char *fmt_f64(double num) +{ + uint64_t double_as_hex = *(uint64_t *) # + char *fmt; + asprintf(&fmt, "f64(%02.20a:%#020" PRIx64 ")", num, double_as_hex); + return fmt; +} + +/* + * Float flags + */ +char *fmt_flags(void) +{ + int flags = fetestexcept(FE_ALL_EXCEPT); + char *fmt; + + if (flags) { + asprintf(&fmt, "%s%s%s%s%s", + flags & FE_OVERFLOW ? "OVERFLOW " : "", + flags & FE_UNDERFLOW ? "UNDERFLOW " : "", + flags & FE_DIVBYZERO ? "DIV0 " : "", + flags & FE_INEXACT ? "INEXACT " : "", + flags & FE_INVALID ? "INVALID" : ""); + } else { + asprintf(&fmt, "OK"); + } + + return fmt; +} diff --git a/tests/tcg/multiarch/linux/linux-test.c b/tests/tcg/multiarch/linux/linux-test.c new file mode 100644 index 000000000..019d8175c --- /dev/null +++ b/tests/tcg/multiarch/linux/linux-test.c @@ -0,0 +1,545 @@ +/* + * linux and CPU test + * + * Copyright (c) 2003 Fabrice Bellard + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#define _GNU_SOURCE +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <inttypes.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <utime.h> +#include <time.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/uio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sched.h> +#include <dirent.h> +#include <setjmp.h> +#include <sys/shm.h> +#include <assert.h> + +#define STACK_SIZE 16384 + +static void error1(const char *filename, int line, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", filename, line); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +static int __chk_error(const char *filename, int line, int ret) +{ + if (ret < 0) { + error1(filename, line, "%m (ret=%d, errno=%d/%s)", + ret, errno, strerror(errno)); + } + return ret; +} + +#define error(fmt, ...) error1(__FILE__, __LINE__, fmt, ## __VA_ARGS__) + +#define chk_error(ret) __chk_error(__FILE__, __LINE__, (ret)) + +/*******************************************************/ + +#define FILE_BUF_SIZE 300 + +static void test_file(void) +{ + int fd, i, len, ret; + uint8_t buf[FILE_BUF_SIZE]; + uint8_t buf2[FILE_BUF_SIZE]; + uint8_t buf3[FILE_BUF_SIZE]; + char cur_dir[1024]; + struct stat st; + struct utimbuf tbuf; + struct iovec vecs[2]; + DIR *dir; + struct dirent64 *de; + /* TODO: make common tempdir creation for tcg tests */ + char template[] = "/tmp/linux-test-XXXXXX"; + char *tmpdir = mkdtemp(template); + + assert(tmpdir); + + if (getcwd(cur_dir, sizeof(cur_dir)) == NULL) + error("getcwd"); + + chk_error(chdir(tmpdir)); + + /* open/read/write/close/readv/writev/lseek */ + + fd = chk_error(open("file1", O_WRONLY | O_TRUNC | O_CREAT, 0644)); + for(i=0;i < FILE_BUF_SIZE; i++) + buf[i] = i; + len = chk_error(write(fd, buf, FILE_BUF_SIZE / 2)); + if (len != (FILE_BUF_SIZE / 2)) + error("write"); + vecs[0].iov_base = buf + (FILE_BUF_SIZE / 2); + vecs[0].iov_len = 16; + vecs[1].iov_base = buf + (FILE_BUF_SIZE / 2) + 16; + vecs[1].iov_len = (FILE_BUF_SIZE / 2) - 16; + len = chk_error(writev(fd, vecs, 2)); + if (len != (FILE_BUF_SIZE / 2)) + error("writev"); + chk_error(close(fd)); + + chk_error(rename("file1", "file2")); + + fd = chk_error(open("file2", O_RDONLY)); + + len = chk_error(read(fd, buf2, FILE_BUF_SIZE)); + if (len != FILE_BUF_SIZE) + error("read"); + if (memcmp(buf, buf2, FILE_BUF_SIZE) != 0) + error("memcmp"); + +#define FOFFSET 16 + ret = chk_error(lseek(fd, FOFFSET, SEEK_SET)); + if (ret != 16) + error("lseek"); + vecs[0].iov_base = buf3; + vecs[0].iov_len = 32; + vecs[1].iov_base = buf3 + 32; + vecs[1].iov_len = FILE_BUF_SIZE - FOFFSET - 32; + len = chk_error(readv(fd, vecs, 2)); + if (len != FILE_BUF_SIZE - FOFFSET) + error("readv"); + if (memcmp(buf + FOFFSET, buf3, FILE_BUF_SIZE - FOFFSET) != 0) + error("memcmp"); + + chk_error(close(fd)); + + /* access */ + chk_error(access("file2", R_OK)); + + /* stat/chmod/utime/truncate */ + + chk_error(chmod("file2", 0600)); + tbuf.actime = 1001; + tbuf.modtime = 1000; + chk_error(truncate("file2", 100)); + chk_error(utime("file2", &tbuf)); + chk_error(stat("file2", &st)); + if (st.st_size != 100) + error("stat size"); + if (!S_ISREG(st.st_mode)) + error("stat mode"); + if ((st.st_mode & 0777) != 0600) + error("stat mode2"); + if (st.st_atime != 1001 || + st.st_mtime != 1000) + error("stat time"); + + chk_error(stat(tmpdir, &st)); + if (!S_ISDIR(st.st_mode)) + error("stat mode"); + + /* fstat */ + fd = chk_error(open("file2", O_RDWR)); + chk_error(ftruncate(fd, 50)); + chk_error(fstat(fd, &st)); + chk_error(close(fd)); + + if (st.st_size != 50) + error("stat size"); + if (!S_ISREG(st.st_mode)) + error("stat mode"); + + /* symlink/lstat */ + chk_error(symlink("file2", "file3")); + chk_error(lstat("file3", &st)); + if (!S_ISLNK(st.st_mode)) + error("stat mode"); + + /* getdents */ + dir = opendir(tmpdir); + if (!dir) + error("opendir"); + len = 0; + for(;;) { + de = readdir64(dir); + if (!de) + break; + if (strcmp(de->d_name, ".") != 0 && + strcmp(de->d_name, "..") != 0 && + strcmp(de->d_name, "file2") != 0 && + strcmp(de->d_name, "file3") != 0) + error("readdir"); + len++; + } + closedir(dir); + if (len != 4) + error("readdir"); + + chk_error(unlink("file3")); + chk_error(unlink("file2")); + chk_error(chdir(cur_dir)); + chk_error(rmdir(tmpdir)); +} + +static void test_fork(void) +{ + int pid, status; + + pid = chk_error(fork()); + if (pid == 0) { + /* child */ + sleep(2); + exit(2); + } + chk_error(waitpid(pid, &status, 0)); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 2) + error("waitpid status=0x%x", status); +} + +static void test_time(void) +{ + struct timeval tv, tv2; + struct timespec ts, rem; + struct rusage rusg1, rusg2; + int ti, i; + + chk_error(gettimeofday(&tv, NULL)); + rem.tv_sec = 1; + ts.tv_sec = 0; + ts.tv_nsec = 20 * 1000000; + chk_error(nanosleep(&ts, &rem)); + if (rem.tv_sec != 1) + error("nanosleep"); + chk_error(gettimeofday(&tv2, NULL)); + ti = tv2.tv_sec - tv.tv_sec; + if (ti >= 2) + error("gettimeofday"); + + chk_error(getrusage(RUSAGE_SELF, &rusg1)); + for(i = 0;i < 10000; i++); + chk_error(getrusage(RUSAGE_SELF, &rusg2)); + if ((rusg2.ru_utime.tv_sec - rusg1.ru_utime.tv_sec) < 0 || + (rusg2.ru_stime.tv_sec - rusg1.ru_stime.tv_sec) < 0) + error("getrusage"); +} + +static int server_socket(void) +{ + int val, fd; + struct sockaddr_in sockaddr = {}; + + /* server socket */ + fd = chk_error(socket(PF_INET, SOCK_STREAM, 0)); + + val = 1; + chk_error(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))); + + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(0); /* choose random ephemeral port) */ + sockaddr.sin_addr.s_addr = 0; + chk_error(bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))); + chk_error(listen(fd, 0)); + return fd; + +} + +static int client_socket(uint16_t port) +{ + int fd; + struct sockaddr_in sockaddr = {}; + + /* server socket */ + fd = chk_error(socket(PF_INET, SOCK_STREAM, 0)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(port); + inet_aton("127.0.0.1", &sockaddr.sin_addr); + chk_error(connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))); + return fd; +} + +static const char socket_msg[] = "hello socket\n"; + +static void test_socket(void) +{ + int server_fd, client_fd, fd, pid, ret, val; + struct sockaddr_in sockaddr; + struct sockaddr_in server_addr; + socklen_t len, socklen; + uint16_t server_port; + char buf[512]; + + server_fd = server_socket(); + /* find out what port we got */ + socklen = sizeof(server_addr); + ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &socklen); + chk_error(ret); + server_port = ntohs(server_addr.sin_port); + + /* test a few socket options */ + len = sizeof(val); + chk_error(getsockopt(server_fd, SOL_SOCKET, SO_TYPE, &val, &len)); + if (val != SOCK_STREAM) + error("getsockopt"); + + pid = chk_error(fork()); + if (pid == 0) { + client_fd = client_socket(server_port); + send(client_fd, socket_msg, sizeof(socket_msg), 0); + close(client_fd); + exit(0); + } + len = sizeof(sockaddr); + fd = chk_error(accept(server_fd, (struct sockaddr *)&sockaddr, &len)); + + ret = chk_error(recv(fd, buf, sizeof(buf), 0)); + if (ret != sizeof(socket_msg)) + error("recv"); + if (memcmp(buf, socket_msg, sizeof(socket_msg)) != 0) + error("socket_msg"); + chk_error(close(fd)); + chk_error(close(server_fd)); +} + +#define WCOUNT_MAX 512 + +static void test_pipe(void) +{ + fd_set rfds, wfds; + int fds[2], fd_max, ret; + uint8_t ch; + int wcount, rcount; + + chk_error(pipe(fds)); + chk_error(fcntl(fds[0], F_SETFL, O_NONBLOCK)); + chk_error(fcntl(fds[1], F_SETFL, O_NONBLOCK)); + wcount = 0; + rcount = 0; + for(;;) { + FD_ZERO(&rfds); + fd_max = fds[0]; + FD_SET(fds[0], &rfds); + + FD_ZERO(&wfds); + FD_SET(fds[1], &wfds); + if (fds[1] > fd_max) + fd_max = fds[1]; + + ret = chk_error(select(fd_max + 1, &rfds, &wfds, NULL, NULL)); + if (ret > 0) { + if (FD_ISSET(fds[0], &rfds)) { + chk_error(read(fds[0], &ch, 1)); + rcount++; + if (rcount >= WCOUNT_MAX) + break; + } + if (FD_ISSET(fds[1], &wfds)) { + ch = 'a'; + chk_error(write(fds[1], &ch, 1)); + wcount++; + } + } + } + chk_error(close(fds[0])); + chk_error(close(fds[1])); +} + +static int thread1_res; +static int thread2_res; + +static int thread1_func(void *arg) +{ + int i; + for(i=0;i<5;i++) { + thread1_res++; + usleep(10 * 1000); + } + return 0; +} + +static int thread2_func(void *arg) +{ + int i; + for(i=0;i<6;i++) { + thread2_res++; + usleep(10 * 1000); + } + return 0; +} + +static void wait_for_child(pid_t pid) +{ + int status; + chk_error(waitpid(pid, &status, 0)); +} + +/* For test_clone we must match the clone flags used by glibc, see + * CLONE_THREAD_FLAGS in the QEMU source code. + */ +static void test_clone(void) +{ + uint8_t *stack1, *stack2; + pid_t pid1, pid2; + + stack1 = malloc(STACK_SIZE); + pid1 = chk_error(clone(thread1_func, stack1 + STACK_SIZE, + CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, + "hello1")); + + stack2 = malloc(STACK_SIZE); + pid2 = chk_error(clone(thread2_func, stack2 + STACK_SIZE, + CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM, + "hello2")); + + wait_for_child(pid1); + free(stack1); + wait_for_child(pid2); + free(stack2); + + if (thread1_res != 5 || + thread2_res != 6) + error("clone"); +} + +/***********************************/ + +volatile int alarm_count; +jmp_buf jmp_env; + +static void sig_alarm(int sig) +{ + if (sig != SIGALRM) + error("signal"); + alarm_count++; +} + +static void sig_segv(int sig, siginfo_t *info, void *puc) +{ + if (sig != SIGSEGV) + error("signal"); + longjmp(jmp_env, 1); +} + +static void test_signal(void) +{ + struct sigaction act; + struct itimerval it, oit; + + /* timer test */ + + alarm_count = 0; + + act.sa_handler = sig_alarm; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + chk_error(sigaction(SIGALRM, &act, NULL)); + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 10 * 1000; + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 10 * 1000; + chk_error(setitimer(ITIMER_REAL, &it, NULL)); + chk_error(getitimer(ITIMER_REAL, &oit)); + + while (alarm_count < 5) { + usleep(10 * 1000); + getitimer(ITIMER_REAL, &oit); + } + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + memset(&oit, 0xff, sizeof(oit)); + chk_error(setitimer(ITIMER_REAL, &it, &oit)); + + /* SIGSEGV test */ + act.sa_sigaction = sig_segv; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + chk_error(sigaction(SIGSEGV, &act, NULL)); + if (setjmp(jmp_env) == 0) { + /* + * clang requires volatile or it will turn this into a + * call to abort() instead of forcing a SIGSEGV. + */ + *(volatile uint8_t *)0 = 0; + } + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + chk_error(sigaction(SIGSEGV, &act, NULL)); + + if (sigaction(SIGKILL, &act, NULL) == 0) { + error("sigaction(SIGKILL, &act, NULL) must not succeed"); + } + if (sigaction(SIGSTOP, &act, NULL) == 0) { + error("sigaction(SIGSTOP, &act, NULL) must not succeed"); + } + chk_error(sigaction(SIGKILL, NULL, &act)); + chk_error(sigaction(SIGSTOP, NULL, &act)); +} + +#define SHM_SIZE 32768 + +static void test_shm(void) +{ + void *ptr; + int shmid; + + shmid = chk_error(shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0777)); + ptr = shmat(shmid, NULL, 0); + if (ptr == (void *)-1) { + error("shmat"); + } + + memset(ptr, 0, SHM_SIZE); + + chk_error(shmctl(shmid, IPC_RMID, 0)); + chk_error(shmdt(ptr)); +} + +int main(int argc, char **argv) +{ + test_file(); + test_pipe(); + test_fork(); + test_time(); + test_socket(); + + if (argc > 1) { + printf("test_clone still considered buggy\n"); + test_clone(); + } + + test_signal(); + test_shm(); + return 0; +} diff --git a/tests/tcg/multiarch/sha1.c b/tests/tcg/multiarch/sha1.c new file mode 100644 index 000000000..0081bd765 --- /dev/null +++ b/tests/tcg/multiarch/sha1.c @@ -0,0 +1,239 @@ + +/* from valgrind tests */ + +/* ================ sha1.c ================ */ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include <stdio.h> +#include <string.h> +#include <stdint.h> + +/* ================ sha1.h ================ */ +/* +SHA-1 in C +By Steve Reid <steve@edmweb.com> +100% Public Domain +*/ + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); +/* ================ end of sha1.h ================ */ + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ +uint32_t a, b, c, d, e; +typedef union { + unsigned char c[64]; + uint32_t l[16]; +} CHAR64LONG16; +#ifdef SHA1HANDSOFF +CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ +CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) +{ +uint32_t i; +uint32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ +unsigned i; +unsigned char finalcount[8]; +unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t; + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +/* ================ end of sha1.c ================ */ + +#define BUFSIZE 4096 + +int +main(int argc, char **argv) +{ + SHA1_CTX ctx; + unsigned char hash[20], buf[BUFSIZE]; + int i; + + for(i=0;i<BUFSIZE;i++) + buf[i] = i; + + SHA1Init(&ctx); + for(i=0;i<1000;i++) + SHA1Update(&ctx, buf, BUFSIZE); + SHA1Final(hash, &ctx); + + printf("SHA1="); + for(i=0;i<20;i++) + printf("%02x", hash[i]); + printf("\n"); + return 0; +} diff --git a/tests/tcg/multiarch/signals.c b/tests/tcg/multiarch/signals.c new file mode 100644 index 000000000..998c8fdef --- /dev/null +++ b/tests/tcg/multiarch/signals.c @@ -0,0 +1,149 @@ +/* + * linux-user signal handling tests. + * + * Copyright (c) 2021 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> +#include <string.h> +#include <signal.h> +#include <time.h> +#include <sys/time.h> + +static void error1(const char *filename, int line, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", filename, line); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +static int __chk_error(const char *filename, int line, int ret) +{ + if (ret < 0) { + error1(filename, line, "%m (ret=%d, errno=%d/%s)", + ret, errno, strerror(errno)); + } + return ret; +} + +#define error(fmt, ...) error1(__FILE__, __LINE__, fmt, ## __VA_ARGS__) + +#define chk_error(ret) __chk_error(__FILE__, __LINE__, (ret)) + +/* + * Thread handling + */ +typedef struct ThreadJob ThreadJob; + +struct ThreadJob { + int number; + int sleep; + int count; +}; + +static pthread_t *threads; +static int max_threads = 10; +__thread int signal_count; +int total_signal_count; + +static void *background_thread_func(void *arg) +{ + ThreadJob *job = (ThreadJob *) arg; + + printf("thread%d: started\n", job->number); + while (total_signal_count < job->count) { + usleep(job->sleep); + } + printf("thread%d: saw %d alarms from %d\n", job->number, + signal_count, total_signal_count); + return NULL; +} + +static void spawn_threads(void) +{ + int i; + threads = calloc(sizeof(pthread_t), max_threads); + + for (i = 0; i < max_threads; i++) { + ThreadJob *job = calloc(sizeof(ThreadJob), 1); + job->number = i; + job->sleep = i * 1000; + job->count = i * 100; + pthread_create(threads + i, NULL, background_thread_func, job); + } +} + +static void close_threads(void) +{ + int i; + for (i = 0; i < max_threads; i++) { + pthread_join(threads[i], NULL); + } + free(threads); + threads = NULL; +} + +static void sig_alarm(int sig, siginfo_t *info, void *puc) +{ + if (sig != SIGRTMIN) { + error("unexpected signal"); + } + signal_count++; + __atomic_fetch_add(&total_signal_count, 1, __ATOMIC_SEQ_CST); +} + +static void test_signals(void) +{ + struct sigaction act; + struct itimerspec it; + timer_t tid; + struct sigevent sev; + + /* Set up SIG handler */ + act.sa_sigaction = sig_alarm; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + chk_error(sigaction(SIGRTMIN, &act, NULL)); + + /* Create POSIX timer */ + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = SIGRTMIN; + sev.sigev_value.sival_ptr = &tid; + chk_error(timer_create(CLOCK_REALTIME, &sev, &tid)); + + it.it_interval.tv_sec = 0; + it.it_interval.tv_nsec = 1000000; + it.it_value.tv_sec = 0; + it.it_value.tv_nsec = 1000000; + chk_error(timer_settime(tid, 0, &it, NULL)); + + spawn_threads(); + + do { + usleep(1000); + } while (total_signal_count < 2000); + + printf("shutting down after: %d signals\n", total_signal_count); + + close_threads(); + + chk_error(timer_delete(tid)); +} + +int main(int argc, char **argv) +{ + test_signals(); + return 0; +} diff --git a/tests/tcg/multiarch/system/Makefile.softmmu-target b/tests/tcg/multiarch/system/Makefile.softmmu-target new file mode 100644 index 000000000..625ed792c --- /dev/null +++ b/tests/tcg/multiarch/system/Makefile.softmmu-target @@ -0,0 +1,35 @@ +# -*- Mode: makefile -*- +# +# Multiarch system tests +# +# We just collect the tests together here and rely on the actual guest +# architecture to add to the test dependancies and deal with the +# complications of building. +# + +MULTIARCH_SRC=$(SRC_PATH)/tests/tcg/multiarch +MULTIARCH_SYSTEM_SRC=$(MULTIARCH_SRC)/system +VPATH+=$(MULTIARCH_SYSTEM_SRC) + +MULTIARCH_TEST_SRCS=$(wildcard $(MULTIARCH_SYSTEM_SRC)/*.c) +MULTIARCH_TESTS = $(patsubst $(MULTIARCH_SYSTEM_SRC)/%.c, %, $(MULTIARCH_TEST_SRCS)) + +ifneq ($(HAVE_GDB_BIN),) +GDB_SCRIPT=$(SRC_PATH)/tests/guest-debug/run-test.py + +run-gdbstub-memory: memory + $(call run-test, $@, $(GDB_SCRIPT) \ + --gdb $(HAVE_GDB_BIN) \ + --qemu $(QEMU) \ + --output $<.gdb.out \ + --qargs \ + "-monitor none -display none -chardev file$(COMMA)path=$<.out$(COMMA)id=output $(QEMU_OPTS)" \ + --bin $< --test $(MULTIARCH_SRC)/gdbstub/memory.py, \ + "softmmu gdbstub support") + +else +run-gdbstub-%: + $(call skip-test, "gdbstub test $*", "need working gdb") +endif + +MULTIARCH_RUNS += run-gdbstub-memory diff --git a/tests/tcg/multiarch/system/hello.c b/tests/tcg/multiarch/system/hello.c new file mode 100644 index 000000000..821dc0ef0 --- /dev/null +++ b/tests/tcg/multiarch/system/hello.c @@ -0,0 +1,14 @@ +/* + * Hello World, system test version + * + * We don't have the benefit of libc, just builtin C primitives and + * whatever is in minilib. + */ + +#include <minilib.h> + +int main(void) +{ + ml_printf("Hello World\n"); + return 0; +} diff --git a/tests/tcg/multiarch/system/memory.c b/tests/tcg/multiarch/system/memory.c new file mode 100644 index 000000000..41c7f66e2 --- /dev/null +++ b/tests/tcg/multiarch/system/memory.c @@ -0,0 +1,476 @@ +/* + * Memory Test + * + * This is intended to test the softmmu code and ensure we properly + * behave across normal and unaligned accesses across several pages. + * We are not replicating memory tests for stuck bits and other + * hardware level failures but looking for issues with different size + * accesses when access is: + * + * - unaligned at various sizes (if -DCHECK_UNALIGNED set) + * - spanning a (softmmu) page + * - sign extension when loading + */ + +#include <inttypes.h> +#include <stdbool.h> +#include <minilib.h> + +#ifndef CHECK_UNALIGNED +# error "Target does not specify CHECK_UNALIGNED" +#endif + +#define MEM_PAGE_SIZE 4096 /* nominal 4k "pages" */ +#define TEST_SIZE (MEM_PAGE_SIZE * 4) /* 4 pages */ + +#define ARRAY_SIZE(x) ((sizeof(x) / sizeof((x)[0]))) + +__attribute__((aligned(MEM_PAGE_SIZE))) +static uint8_t test_data[TEST_SIZE]; + +typedef void (*init_ufn) (int offset); +typedef bool (*read_ufn) (int offset); +typedef bool (*read_sfn) (int offset, bool nf); + +static void pdot(int count) +{ + if (count % 128 == 0) { + ml_printf("."); + } +} + +/* + * Helper macros for shift/extract so we can keep our endian handling + * in one place. + */ +#define BYTE_SHIFT(b, pos) ((uint64_t)b << (pos * 8)) +#define BYTE_EXTRACT(b, pos) ((b >> (pos * 8)) & 0xff) + +/* + * Fill the data with ascending value bytes. + * + * Currently we only support Little Endian machines so write in + * ascending address order. When we read higher address bytes should + * either be zero or higher than the lower bytes. + */ + +static void init_test_data_u8(int unused_offset) +{ + uint8_t count = 0, *ptr = &test_data[0]; + int i; + (void)(unused_offset); + + ml_printf("Filling test area with u8:"); + for (i = 0; i < TEST_SIZE; i++) { + *ptr++ = count++; + pdot(i); + } + ml_printf("done\n"); +} + +/* + * Full the data with alternating positive and negative bytes. This + * should mean for reads larger than a byte all subsequent reads will + * stay either negative or positive. We never write 0. + */ + +static inline uint8_t get_byte(int index, bool neg) +{ + return neg ? (0xff << (index % 7)) : (0xff >> ((index % 6) + 1)); +} + +static void init_test_data_s8(bool neg_first) +{ + uint8_t top, bottom, *ptr = &test_data[0]; + int i; + + ml_printf("Filling test area with s8 pairs (%s):", + neg_first ? "neg first" : "pos first"); + for (i = 0; i < TEST_SIZE / 2; i++) { + *ptr++ = get_byte(i, neg_first); + *ptr++ = get_byte(i, !neg_first); + pdot(i); + } + ml_printf("done\n"); +} + +/* + * Zero the first few bytes of the test data in preparation for + * new offset values. + */ +static void reset_start_data(int offset) +{ + uint32_t *ptr = (uint32_t *) &test_data[0]; + int i; + for (i = 0; i < offset; i++) { + *ptr++ = 0; + } +} + +static void init_test_data_u16(int offset) +{ + uint8_t count = 0; + uint16_t word, *ptr = (uint16_t *) &test_data[offset]; + const int max = (TEST_SIZE - offset) / sizeof(word); + int i; + + ml_printf("Filling test area with u16 (offset %d, %p):", offset, ptr); + + reset_start_data(offset); + + for (i = 0; i < max; i++) { + uint8_t low = count++, high = count++; + word = BYTE_SHIFT(high, 1) | BYTE_SHIFT(low, 0); + *ptr++ = word; + pdot(i); + } + ml_printf("done @ %p\n", ptr); +} + +static void init_test_data_u32(int offset) +{ + uint8_t count = 0; + uint32_t word, *ptr = (uint32_t *) &test_data[offset]; + const int max = (TEST_SIZE - offset) / sizeof(word); + int i; + + ml_printf("Filling test area with u32 (offset %d, %p):", offset, ptr); + + reset_start_data(offset); + + for (i = 0; i < max; i++) { + uint8_t b4 = count++, b3 = count++; + uint8_t b2 = count++, b1 = count++; + word = BYTE_SHIFT(b1, 3) | BYTE_SHIFT(b2, 2) | BYTE_SHIFT(b3, 1) | b4; + *ptr++ = word; + pdot(i); + } + ml_printf("done @ %p\n", ptr); +} + +static void init_test_data_u64(int offset) +{ + uint8_t count = 0; + uint64_t word, *ptr = (uint64_t *) &test_data[offset]; + const int max = (TEST_SIZE - offset) / sizeof(word); + int i; + + ml_printf("Filling test area with u64 (offset %d, %p):", offset, ptr); + + reset_start_data(offset); + + for (i = 0; i < max; i++) { + uint8_t b8 = count++, b7 = count++; + uint8_t b6 = count++, b5 = count++; + uint8_t b4 = count++, b3 = count++; + uint8_t b2 = count++, b1 = count++; + word = BYTE_SHIFT(b1, 7) | BYTE_SHIFT(b2, 6) | BYTE_SHIFT(b3, 5) | + BYTE_SHIFT(b4, 4) | BYTE_SHIFT(b5, 3) | BYTE_SHIFT(b6, 2) | + BYTE_SHIFT(b7, 1) | b8; + *ptr++ = word; + pdot(i); + } + ml_printf("done @ %p\n", ptr); +} + +static bool read_test_data_u16(int offset) +{ + uint16_t word, *ptr = (uint16_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / sizeof(word); + + ml_printf("Reading u16 from %#lx (offset %d):", ptr, offset); + + for (i = 0; i < max; i++) { + uint8_t high, low; + word = *ptr++; + high = (word >> 8) & 0xff; + low = word & 0xff; + if (high < low && high != 0) { + ml_printf("Error %d < %d\n", high, low); + return false; + } else { + pdot(i); + } + + } + ml_printf("done @ %p\n", ptr); + return true; +} + +static bool read_test_data_u32(int offset) +{ + uint32_t word, *ptr = (uint32_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / sizeof(word); + + ml_printf("Reading u32 from %#lx (offset %d):", ptr, offset); + + for (i = 0; i < max; i++) { + uint8_t b1, b2, b3, b4; + int zeros = 0; + word = *ptr++; + + b1 = word >> 24 & 0xff; + b2 = word >> 16 & 0xff; + b3 = word >> 8 & 0xff; + b4 = word & 0xff; + + zeros += (b1 == 0 ? 1 : 0); + zeros += (b2 == 0 ? 1 : 0); + zeros += (b3 == 0 ? 1 : 0); + zeros += (b4 == 0 ? 1 : 0); + if (zeros > 1) { + ml_printf("Error @ %p, more zeros than expected: %d, %d, %d, %d", + ptr - 1, b1, b2, b3, b4); + return false; + } + + if ((b1 < b2 && b1 != 0) || + (b2 < b3 && b2 != 0) || + (b3 < b4 && b3 != 0)) { + ml_printf("Error %d, %d, %d, %d", b1, b2, b3, b4); + return false; + } else { + pdot(i); + } + } + ml_printf("done @ %p\n", ptr); + return true; +} + +static bool read_test_data_u64(int offset) +{ + uint64_t word, *ptr = (uint64_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / sizeof(word); + + ml_printf("Reading u64 from %#lx (offset %d):", ptr, offset); + + for (i = 0; i < max; i++) { + uint8_t b1, b2, b3, b4, b5, b6, b7, b8; + int zeros = 0; + word = *ptr++; + + b1 = ((uint64_t) (word >> 56)) & 0xff; + b2 = ((uint64_t) (word >> 48)) & 0xff; + b3 = ((uint64_t) (word >> 40)) & 0xff; + b4 = (word >> 32) & 0xff; + b5 = (word >> 24) & 0xff; + b6 = (word >> 16) & 0xff; + b7 = (word >> 8) & 0xff; + b8 = (word >> 0) & 0xff; + + zeros += (b1 == 0 ? 1 : 0); + zeros += (b2 == 0 ? 1 : 0); + zeros += (b3 == 0 ? 1 : 0); + zeros += (b4 == 0 ? 1 : 0); + zeros += (b5 == 0 ? 1 : 0); + zeros += (b6 == 0 ? 1 : 0); + zeros += (b7 == 0 ? 1 : 0); + zeros += (b8 == 0 ? 1 : 0); + if (zeros > 1) { + ml_printf("Error @ %p, more zeros than expected: %d, %d, %d, %d, %d, %d, %d, %d", + ptr - 1, b1, b2, b3, b4, b5, b6, b7, b8); + return false; + } + + if ((b1 < b2 && b1 != 0) || + (b2 < b3 && b2 != 0) || + (b3 < b4 && b3 != 0) || + (b4 < b5 && b4 != 0) || + (b5 < b6 && b5 != 0) || + (b6 < b7 && b6 != 0) || + (b7 < b8 && b7 != 0)) { + ml_printf("Error %d, %d, %d, %d, %d, %d, %d, %d", + b1, b2, b3, b4, b5, b6, b7, b8); + return false; + } else { + pdot(i); + } + } + ml_printf("done @ %p\n", ptr); + return true; +} + +/* Read the test data and verify at various offsets */ +read_ufn read_ufns[] = { read_test_data_u16, + read_test_data_u32, + read_test_data_u64 }; + +bool do_unsigned_reads(int start_off) +{ + int i; + bool ok = true; + + for (i = 0; i < ARRAY_SIZE(read_ufns) && ok; i++) { +#if CHECK_UNALIGNED + int off; + for (off = start_off; off < 8 && ok; off++) { + ok = read_ufns[i](off); + } +#else + ok = read_ufns[i](start_off); +#endif + } + + return ok; +} + +static bool do_unsigned_test(init_ufn fn) +{ +#if CHECK_UNALIGNED + bool ok = true; + int i; + for (i = 0; i < 8 && ok; i++) { + fn(i); + ok = do_unsigned_reads(i); + } + return ok; +#else + fn(0); + return do_unsigned_reads(0); +#endif +} + +/* + * We need to ensure signed data is read into a larger data type to + * ensure that sign extension is working properly. + */ + +static bool read_test_data_s8(int offset, bool neg_first) +{ + int8_t *ptr = (int8_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / 2; + + ml_printf("Reading s8 pairs from %#lx (offset %d):", ptr, offset); + + for (i = 0; i < max; i++) { + int16_t first, second; + bool ok; + first = *ptr++; + second = *ptr++; + + if (neg_first && first < 0 && second > 0) { + pdot(i); + } else if (!neg_first && first > 0 && second < 0) { + pdot(i); + } else { + ml_printf("Error %d %c %d\n", first, neg_first ? '<' : '>', second); + return false; + } + } + ml_printf("done @ %p\n", ptr); + return true; +} + +static bool read_test_data_s16(int offset, bool neg_first) +{ + int16_t *ptr = (int16_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / (sizeof(*ptr)); + + ml_printf("Reading s16 from %#lx (offset %d, %s):", ptr, + offset, neg_first ? "neg" : "pos"); + + for (i = 0; i < max; i++) { + int32_t data = *ptr++; + + if (neg_first && data < 0) { + pdot(i); + } else if (data > 0) { + pdot(i); + } else { + ml_printf("Error %d %c 0\n", data, neg_first ? '<' : '>'); + return false; + } + } + ml_printf("done @ %p\n", ptr); + return true; +} + +static bool read_test_data_s32(int offset, bool neg_first) +{ + int32_t *ptr = (int32_t *)&test_data[offset]; + int i; + const int max = (TEST_SIZE - offset) / (sizeof(int32_t)); + + ml_printf("Reading s32 from %#lx (offset %d, %s):", + ptr, offset, neg_first ? "neg" : "pos"); + + for (i = 0; i < max; i++) { + int64_t data = *ptr++; + + if (neg_first && data < 0) { + pdot(i); + } else if (data > 0) { + pdot(i); + } else { + ml_printf("Error %d %c 0\n", data, neg_first ? '<' : '>'); + return false; + } + } + ml_printf("done @ %p\n", ptr); + return true; +} + +/* + * Read the test data and verify at various offsets + * + * For everything except bytes all our reads should be either positive + * or negative depending on what offset we are reading from. Currently + * we only handle LE systems. + */ +read_sfn read_sfns[] = { read_test_data_s8, + read_test_data_s16, + read_test_data_s32 }; + +bool do_signed_reads(bool neg_first) +{ + int i; + bool ok = true; + + for (i = 0; i < ARRAY_SIZE(read_sfns) && ok; i++) { +#if CHECK_UNALIGNED + int off; + for (off = 0; off < 8 && ok; off++) { + bool nf = i == 0 ? neg_first ^ (off & 1) : !(neg_first ^ (off & 1)); + ok = read_sfns[i](off, nf); + } +#else + ok = read_sfns[i](0, i == 0 ? neg_first : !neg_first); +#endif + } + + return ok; +} + +init_ufn init_ufns[] = { init_test_data_u8, + init_test_data_u16, + init_test_data_u32, + init_test_data_u64 }; + +int main(void) +{ + int i; + bool ok = true; + + /* Run through the unsigned tests first */ + for (i = 0; i < ARRAY_SIZE(init_ufns) && ok; i++) { + ok = do_unsigned_test(init_ufns[i]); + } + + if (ok) { + init_test_data_s8(false); + ok = do_signed_reads(false); + } + + if (ok) { + init_test_data_s8(true); + ok = do_signed_reads(true); + } + + ml_printf("Test complete: %s\n", ok ? "PASSED" : "FAILED"); + return ok ? 0 : -1; +} diff --git a/tests/tcg/multiarch/test-mmap.c b/tests/tcg/multiarch/test-mmap.c new file mode 100644 index 000000000..96257f8eb --- /dev/null +++ b/tests/tcg/multiarch/test-mmap.c @@ -0,0 +1,504 @@ +/* + * Small test program to verify simulated mmap behaviour. + * + * When running qemu-linux-user with the -p flag, you may need to tell + * this test program about the pagesize because getpagesize() will not reflect + * the -p choice. Simply pass one argument being the pagesize. + * + * Copyright (c) 2007 AXIS Communications AB + * Written by Edgar E. Iglesias. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/mman.h> + +#define D(x) + +#define fail_unless(x) \ +do \ +{ \ + if (!(x)) { \ + fprintf(stderr, "FAILED at %s:%d\n", __FILE__, __LINE__); \ + exit (EXIT_FAILURE); \ + } \ +} while (0) + +unsigned char *dummybuf; +static unsigned int pagesize; +static unsigned int pagemask; +int test_fd; +size_t test_fsize; + +void check_aligned_anonymous_unfixed_mmaps(void) +{ + void *p1; + void *p2; + void *p3; + void *p4; + void *p5; + uintptr_t p; + int i; + fprintf(stdout, "%s", __func__); + for (i = 0; i < 8; i++) { + size_t len; + len = pagesize + (pagesize * i); + p1 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + p2 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + p3 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + p4 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + p5 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + /* + * Make sure we get pages aligned with the pagesize. The + * target expects this. + */ + fail_unless(p1 != MAP_FAILED); + fail_unless(p2 != MAP_FAILED); + fail_unless(p3 != MAP_FAILED); + fail_unless(p4 != MAP_FAILED); + fail_unless(p5 != MAP_FAILED); + p = (uintptr_t) p1; + D(printf("p=%x\n", p)); + fail_unless((p & pagemask) == 0); + p = (uintptr_t) p2; + fail_unless((p & pagemask) == 0); + p = (uintptr_t) p3; + fail_unless((p & pagemask) == 0); + p = (uintptr_t) p4; + fail_unless((p & pagemask) == 0); + p = (uintptr_t) p5; + fail_unless((p & pagemask) == 0); + + /* Make sure we can read from the entire area. */ + memcpy(dummybuf, p1, pagesize); + memcpy(dummybuf, p2, pagesize); + memcpy(dummybuf, p3, pagesize); + memcpy(dummybuf, p4, pagesize); + memcpy(dummybuf, p5, pagesize); + munmap(p1, len); + munmap(p2, len); + munmap(p3, len); + munmap(p4, len); + munmap(p5, len); + } + fprintf(stdout, " passed\n"); +} + +void check_large_anonymous_unfixed_mmap(void) +{ + void *p1; + uintptr_t p; + size_t len; + + fprintf(stdout, "%s", __func__); + + len = 0x02000000; + p1 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + /* Make sure we get pages aligned with the pagesize. The + target expects this. */ + fail_unless (p1 != MAP_FAILED); + p = (uintptr_t) p1; + fail_unless ((p & pagemask) == 0); + + /* Make sure we can read from the entire area. */ + memcpy (dummybuf, p1, pagesize); + munmap (p1, len); + fprintf(stdout, " passed\n"); +} + +void check_aligned_anonymous_unfixed_colliding_mmaps(void) +{ + char *p1; + char *p2; + char *p3; + uintptr_t p; + int i; + + fprintf(stdout, "%s", __func__); + for (i = 0; i < 2; i++) { + int nlen; + p1 = mmap(NULL, pagesize, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fail_unless(p1 != MAP_FAILED); + p = (uintptr_t) p1; + fail_unless((p & pagemask) == 0); + memcpy(dummybuf, p1, pagesize); + + p2 = mmap(NULL, pagesize, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fail_unless(p2 != MAP_FAILED); + p = (uintptr_t) p2; + fail_unless((p & pagemask) == 0); + memcpy(dummybuf, p2, pagesize); + + + munmap(p1, pagesize); + nlen = pagesize * 8; + p3 = mmap(NULL, nlen, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fail_unless(p3 != MAP_FAILED); + + /* Check if the mmaped areas collide. */ + if (p3 < p2 + && (p3 + nlen) > p2) { + fail_unless(0); + } + + memcpy(dummybuf, p3, pagesize); + + /* + * Make sure we get pages aligned with the pagesize. The + * target expects this. + */ + p = (uintptr_t) p3; + fail_unless((p & pagemask) == 0); + munmap(p2, pagesize); + munmap(p3, nlen); + } + fprintf(stdout, " passed\n"); +} + +void check_aligned_anonymous_fixed_mmaps(void) +{ + char *addr; + void *p1; + uintptr_t p; + int i; + + /* Find a suitable address to start with. */ + addr = mmap(NULL, pagesize * 40, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + fprintf(stdout, "%s addr=%p", __func__, addr); + fail_unless (addr != MAP_FAILED); + + for (i = 0; i < 40; i++) + { + /* Create submaps within our unfixed map. */ + p1 = mmap(addr, pagesize, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0); + /* Make sure we get pages aligned with the pagesize. + The target expects this. */ + p = (uintptr_t) p1; + fail_unless (p1 == addr); + fail_unless ((p & pagemask) == 0); + memcpy (dummybuf, p1, pagesize); + munmap (p1, pagesize); + addr += pagesize; + } + fprintf(stdout, " passed\n"); +} + +void check_aligned_anonymous_fixed_mmaps_collide_with_host(void) +{ + char *addr; + void *p1; + uintptr_t p; + int i; + + /* Find a suitable address to start with. Right were the x86 hosts + stack is. */ + addr = ((void *)0x80000000); + fprintf(stdout, "%s addr=%p", __func__, addr); + fprintf(stdout, "FIXME: QEMU fails to track pages used by the host."); + + for (i = 0; i < 20; i++) + { + /* Create submaps within our unfixed map. */ + p1 = mmap(addr, pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + -1, 0); + /* Make sure we get pages aligned with the pagesize. + The target expects this. */ + p = (uintptr_t) p1; + fail_unless (p1 == addr); + fail_unless ((p & pagemask) == 0); + memcpy (p1, dummybuf, pagesize); + munmap (p1, pagesize); + addr += pagesize; + } + fprintf(stdout, " passed\n"); +} + +void check_file_unfixed_mmaps(void) +{ + unsigned int *p1, *p2, *p3; + uintptr_t p; + int i; + + fprintf(stdout, "%s", __func__); + for (i = 0; i < 0x10; i++) + { + size_t len; + + len = pagesize; + p1 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE, + test_fd, 0); + p2 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE, + test_fd, pagesize); + p3 = mmap(NULL, len, PROT_READ, + MAP_PRIVATE, + test_fd, pagesize * 2); + + fail_unless (p1 != MAP_FAILED); + fail_unless (p2 != MAP_FAILED); + fail_unless (p3 != MAP_FAILED); + + /* Make sure we get pages aligned with the pagesize. The + target expects this. */ + p = (uintptr_t) p1; + fail_unless ((p & pagemask) == 0); + p = (uintptr_t) p2; + fail_unless ((p & pagemask) == 0); + p = (uintptr_t) p3; + fail_unless ((p & pagemask) == 0); + + /* Verify that the file maps was made correctly. */ + D(printf ("p1=%d p2=%d p3=%d\n", *p1, *p2, *p3)); + fail_unless (*p1 == 0); + fail_unless (*p2 == (pagesize / sizeof *p2)); + fail_unless (*p3 == ((pagesize * 2) / sizeof *p3)); + + memcpy (dummybuf, p1, pagesize); + memcpy (dummybuf, p2, pagesize); + memcpy (dummybuf, p3, pagesize); + munmap (p1, len); + munmap (p2, len); + munmap (p3, len); + } + fprintf(stdout, " passed\n"); +} + +void check_file_unfixed_eof_mmaps(void) +{ + char *cp; + unsigned int *p1; + uintptr_t p; + int i; + + fprintf(stdout, "%s", __func__); + for (i = 0; i < 0x10; i++) + { + p1 = mmap(NULL, pagesize, PROT_READ, + MAP_PRIVATE, + test_fd, + (test_fsize - sizeof *p1) & ~pagemask); + + fail_unless (p1 != MAP_FAILED); + + /* Make sure we get pages aligned with the pagesize. The + target expects this. */ + p = (uintptr_t) p1; + fail_unless ((p & pagemask) == 0); + /* Verify that the file maps was made correctly. */ + fail_unless (p1[(test_fsize & pagemask) / sizeof *p1 - 1] + == ((test_fsize - sizeof *p1) / sizeof *p1)); + + /* Verify that the end of page is accessible and zeroed. */ + cp = (void *) p1; + fail_unless (cp[pagesize - 4] == 0); + munmap (p1, pagesize); + } + fprintf(stdout, " passed\n"); +} + +void check_file_fixed_eof_mmaps(void) +{ + char *addr; + char *cp; + unsigned int *p1; + uintptr_t p; + int i; + + /* Find a suitable address to start with. */ + addr = mmap(NULL, pagesize * 44, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + + fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fail_unless (addr != MAP_FAILED); + + for (i = 0; i < 0x10; i++) + { + /* Create submaps within our unfixed map. */ + p1 = mmap(addr, pagesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, + test_fd, + (test_fsize - sizeof *p1) & ~pagemask); + + fail_unless (p1 != MAP_FAILED); + + /* Make sure we get pages aligned with the pagesize. The + target expects this. */ + p = (uintptr_t) p1; + fail_unless ((p & pagemask) == 0); + + /* Verify that the file maps was made correctly. */ + fail_unless (p1[(test_fsize & pagemask) / sizeof *p1 - 1] + == ((test_fsize - sizeof *p1) / sizeof *p1)); + + /* Verify that the end of page is accessible and zeroed. */ + cp = (void *)p1; + fail_unless (cp[pagesize - 4] == 0); + munmap (p1, pagesize); + addr += pagesize; + } + fprintf(stdout, " passed\n"); +} + +void check_file_fixed_mmaps(void) +{ + unsigned char *addr; + unsigned int *p1, *p2, *p3, *p4; + int i; + + /* Find a suitable address to start with. */ + addr = mmap(NULL, pagesize * 40 * 4, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fail_unless (addr != MAP_FAILED); + + for (i = 0; i < 40; i++) + { + p1 = mmap(addr, pagesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, + test_fd, 0); + p2 = mmap(addr + pagesize, pagesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, + test_fd, pagesize); + p3 = mmap(addr + pagesize * 2, pagesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, + test_fd, pagesize * 2); + p4 = mmap(addr + pagesize * 3, pagesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, + test_fd, pagesize * 3); + + /* Make sure we get pages aligned with the pagesize. + The target expects this. */ + fail_unless (p1 == (void *)addr); + fail_unless (p2 == (void *)addr + pagesize); + fail_unless (p3 == (void *)addr + pagesize * 2); + fail_unless (p4 == (void *)addr + pagesize * 3); + + /* Verify that the file maps was made correctly. */ + fail_unless (*p1 == 0); + fail_unless (*p2 == (pagesize / sizeof *p2)); + fail_unless (*p3 == ((pagesize * 2) / sizeof *p3)); + fail_unless (*p4 == ((pagesize * 3) / sizeof *p4)); + + memcpy (dummybuf, p1, pagesize); + memcpy (dummybuf, p2, pagesize); + memcpy (dummybuf, p3, pagesize); + memcpy (dummybuf, p4, pagesize); + + munmap (p1, pagesize); + munmap (p2, pagesize); + munmap (p3, pagesize); + munmap (p4, pagesize); + addr += pagesize * 4; + } + fprintf(stdout, " passed\n"); +} + +void checked_write(int fd, const void *buf, size_t count) +{ + ssize_t rc = write(fd, buf, count); + fail_unless(rc == count); +} + +void check_invalid_mmaps(void) +{ + unsigned char *addr; + + /* Attempt to map a zero length page. */ + addr = mmap(NULL, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fail_unless(addr == MAP_FAILED); + fail_unless(errno == EINVAL); + + /* Attempt to map a over length page. */ + addr = mmap(NULL, -4, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + fprintf(stdout, "%s addr=%p", __func__, (void *)addr); + fail_unless(addr == MAP_FAILED); + fail_unless(errno == ENOMEM); + + fprintf(stdout, " passed\n"); +} + +int main(int argc, char **argv) +{ + char tempname[] = "/tmp/.cmmapXXXXXX"; + unsigned int i; + + /* Trust the first argument, otherwise probe the system for our + pagesize. */ + if (argc > 1) + pagesize = strtoul(argv[1], NULL, 0); + else + pagesize = sysconf(_SC_PAGESIZE); + + /* Assume pagesize is a power of two. */ + pagemask = pagesize - 1; + dummybuf = malloc (pagesize); + printf ("pagesize=%u pagemask=%x\n", pagesize, pagemask); + + test_fd = mkstemp(tempname); + unlink(tempname); + + /* Fill the file with int's counting from zero and up. */ + for (i = 0; i < (pagesize * 4) / sizeof i; i++) { + checked_write(test_fd, &i, sizeof i); + } + + /* Append a few extra writes to make the file end at non + page boundary. */ + checked_write(test_fd, &i, sizeof i); i++; + checked_write(test_fd, &i, sizeof i); i++; + checked_write(test_fd, &i, sizeof i); i++; + + test_fsize = lseek(test_fd, 0, SEEK_CUR); + + /* Run the tests. */ + check_aligned_anonymous_unfixed_mmaps(); + check_aligned_anonymous_unfixed_colliding_mmaps(); + check_aligned_anonymous_fixed_mmaps(); + check_file_unfixed_mmaps(); + check_file_fixed_mmaps(); + check_file_fixed_eof_mmaps(); + check_file_unfixed_eof_mmaps(); + check_invalid_mmaps(); + + /* Fails at the moment. */ + /* check_aligned_anonymous_fixed_mmaps_collide_with_host(); */ + + return EXIT_SUCCESS; +} diff --git a/tests/tcg/multiarch/testthread.c b/tests/tcg/multiarch/testthread.c new file mode 100644 index 000000000..810ba5de6 --- /dev/null +++ b/tests/tcg/multiarch/testthread.c @@ -0,0 +1,57 @@ +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <pthread.h> +#include <sys/wait.h> +#include <sched.h> + +void checked_write(int fd, const void *buf, size_t count) +{ + ssize_t rc = write(fd, buf, count); + assert(rc == count); +} + +void *thread1_func(void *arg) +{ + int i; + char buf[512]; + + for(i=0;i<10;i++) { + snprintf(buf, sizeof(buf), "thread1: %d %s\n", i, (char *)arg); + checked_write(1, buf, strlen(buf)); + usleep(100 * 1000); + } + return NULL; +} + +void *thread2_func(void *arg) +{ + int i; + char buf[512]; + for(i=0;i<20;i++) { + snprintf(buf, sizeof(buf), "thread2: %d %s\n", i, (char *)arg); + checked_write(1, buf, strlen(buf)); + usleep(150 * 1000); + } + return NULL; +} + +void test_pthread(void) +{ + pthread_t tid1, tid2; + + pthread_create(&tid1, NULL, thread1_func, "hello1"); + pthread_create(&tid2, NULL, thread2_func, "hello2"); + pthread_join(tid1, NULL); + pthread_join(tid2, NULL); + printf("End of pthread test.\n"); +} + +int main(int argc, char **argv) +{ + test_pthread(); + return 0; +} diff --git a/tests/tcg/multiarch/threadcount.c b/tests/tcg/multiarch/threadcount.c new file mode 100644 index 000000000..545a1c814 --- /dev/null +++ b/tests/tcg/multiarch/threadcount.c @@ -0,0 +1,64 @@ +/* + * Thread Exerciser + * + * Unlike testthread which is mainly concerned about testing thread + * semantics this test is used to exercise the thread creation and + * accounting. A version of this test found a problem with clashing + * cpu_indexes which caused a break in plugin handling. + * + * Based on the original test case by Nikolay Igotti. + * + * Copyright (c) 2020 Linaro Ltd + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> + +int max_threads = 10; + +typedef struct { + int delay; +} ThreadArg; + +static void *thread_fn(void* varg) +{ + ThreadArg *arg = varg; + usleep(arg->delay); + free(arg); + return NULL; +} + +int main(int argc, char **argv) +{ + int i; + pthread_t *threads; + + if (argc > 1) { + max_threads = atoi(argv[1]); + } + threads = calloc(sizeof(pthread_t), max_threads); + + for (i = 0; i < max_threads; i++) { + ThreadArg *arg = calloc(sizeof(ThreadArg), 1); + arg->delay = i * 100; + pthread_create(threads + i, NULL, thread_fn, arg); + } + + printf("Created %d threads\n", max_threads); + + /* sleep until roughly half the threads have "finished" */ + usleep(max_threads * 50); + + for (i = 0; i < max_threads; i++) { + pthread_join(threads[i], NULL); + } + + printf("Done\n"); + + return 0; +} |