aboutsummaryrefslogtreecommitdiff
path: root/tests/tcg/multiarch
diff options
context:
space:
mode:
Diffstat (limited to 'tests/tcg/multiarch')
-rw-r--r--tests/tcg/multiarch/Makefile.target31
-rwxr-xr-xtests/tcg/multiarch/check-plugin-output.sh36
-rw-r--r--tests/tcg/multiarch/gdbstub/interrupt.py4
-rw-r--r--tests/tcg/multiarch/gdbstub/late-attach.py28
-rw-r--r--tests/tcg/multiarch/gdbstub/prot-none.py4
-rw-r--r--tests/tcg/multiarch/gdbstub/test-proc-mappings.py19
-rw-r--r--tests/tcg/multiarch/late-attach.c41
-rw-r--r--tests/tcg/multiarch/linux/linux-sigrtminmax.c74
-rw-r--r--tests/tcg/multiarch/linux/test-vma.c (renamed from tests/tcg/multiarch/test-vma.c)0
-rw-r--r--tests/tcg/multiarch/sigreturn-sigmask.c51
-rw-r--r--tests/tcg/multiarch/system/Makefile.softmmu-target6
-rw-r--r--tests/tcg/multiarch/system/memory.c122
-rwxr-xr-xtests/tcg/multiarch/system/validate-memory-counts.py130
-rw-r--r--tests/tcg/multiarch/test-plugin-mem-access.c177
14 files changed, 665 insertions, 58 deletions
diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target
index 5e3391e..45c9cfe 100644
--- a/tests/tcg/multiarch/Makefile.target
+++ b/tests/tcg/multiarch/Makefile.target
@@ -42,6 +42,17 @@ munmap-pthread: LDFLAGS+=-pthread
vma-pthread: CFLAGS+=-pthread
vma-pthread: LDFLAGS+=-pthread
+sigreturn-sigmask: CFLAGS+=-pthread
+sigreturn-sigmask: LDFLAGS+=-pthread
+
+# GCC versions 12/13/14/15 at least incorrectly complain about
+# "'SHA1Transform' reading 64 bytes from a region of size 0"; see the gcc bug
+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106709
+# Since this is just a standard piece of library code we've borrowed for a
+# TCG test case, suppress the warning rather than trying to modify the
+# code to work around the compiler.
+sha1: CFLAGS+=-Wno-stringop-overread -Wno-unknown-warning-option
+
# The vma-pthread seems very sensitive on gitlab and we currently
# don't know if its exposing a real bug or the test is flaky.
ifneq ($(GITLAB_CI),)
@@ -127,6 +138,13 @@ run-gdbstub-follow-fork-mode-parent: follow-fork-mode
--bin $< --test $(MULTIARCH_SRC)/gdbstub/follow-fork-mode-parent.py, \
following parents on fork)
+run-gdbstub-late-attach: late-attach
+ $(call run-test, $@, env LATE_ATTACH_PY=1 $(GDB_SCRIPT) \
+ --gdb $(GDB) \
+ --qemu $(QEMU) --qargs "$(QEMU_OPTS)" --no-suspend \
+ --bin $< --test $(MULTIARCH_SRC)/gdbstub/late-attach.py, \
+ attaching to a running process)
+
else
run-gdbstub-%:
$(call skip-test, "gdbstub test $*", "need working gdb with $(patsubst -%,,$(TARGET_NAME)) support")
@@ -136,7 +154,7 @@ EXTRA_RUNS += run-gdbstub-sha1 run-gdbstub-qxfer-auxv-read \
run-gdbstub-registers run-gdbstub-prot-none \
run-gdbstub-catch-syscalls run-gdbstub-follow-fork-mode-child \
run-gdbstub-follow-fork-mode-parent \
- run-gdbstub-qxfer-siginfo-read
+ run-gdbstub-qxfer-siginfo-read run-gdbstub-late-attach
# ARM Compatible Semi Hosting Tests
#
@@ -170,5 +188,16 @@ run-plugin-semiconsole-with-%:
TESTS += semihosting semiconsole
endif
+# Test plugin memory access instrumentation
+run-plugin-test-plugin-mem-access-with-libmem.so: \
+ PLUGIN_ARGS=$(COMMA)print-accesses=true
+run-plugin-test-plugin-mem-access-with-libmem.so: \
+ CHECK_PLUGIN_OUTPUT_COMMAND= \
+ $(SRC_PATH)/tests/tcg/multiarch/check-plugin-output.sh \
+ $(QEMU) $<
+
+test-plugin-mem-access: CFLAGS+=-pthread -O0
+test-plugin-mem-access: LDFLAGS+=-pthread -O0
+
# Update TESTS
TESTS += $(MULTIARCH_TESTS)
diff --git a/tests/tcg/multiarch/check-plugin-output.sh b/tests/tcg/multiarch/check-plugin-output.sh
new file mode 100755
index 0000000..80607f0
--- /dev/null
+++ b/tests/tcg/multiarch/check-plugin-output.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+# This script runs a given executable using qemu, and compare its standard
+# output with an expected plugin output.
+# Each line of output is searched (as a regexp) in the expected plugin output.
+
+set -euo pipefail
+
+die()
+{
+ echo "$@" 1>&2
+ exit 1
+}
+
+check()
+{
+ file=$1
+ pattern=$2
+ grep "$pattern" "$file" > /dev/null || die "\"$pattern\" not found in $file"
+}
+
+[ $# -eq 3 ] || die "usage: qemu_bin exe plugin_out_file"
+
+qemu_bin=$1; shift
+exe=$1;shift
+plugin_out=$1; shift
+
+expected()
+{
+ $qemu_bin $exe ||
+ die "running $exe failed"
+}
+
+expected | while read line; do
+ check "$plugin_out" "$line"
+done
diff --git a/tests/tcg/multiarch/gdbstub/interrupt.py b/tests/tcg/multiarch/gdbstub/interrupt.py
index 90a45b5..2d5654d 100644
--- a/tests/tcg/multiarch/gdbstub/interrupt.py
+++ b/tests/tcg/multiarch/gdbstub/interrupt.py
@@ -8,7 +8,7 @@ from __future__ import print_function
#
import gdb
-from test_gdbstub import main, report
+from test_gdbstub import gdb_exit, main, report
def check_interrupt(thread):
@@ -49,7 +49,7 @@ def run_test():
"""
if len(gdb.selected_inferior().threads()) == 1:
print("SKIP: set to run on a single thread")
- exit(0)
+ gdb_exit(0)
gdb.execute("set scheduler-locking on")
for thread in gdb.selected_inferior().threads():
diff --git a/tests/tcg/multiarch/gdbstub/late-attach.py b/tests/tcg/multiarch/gdbstub/late-attach.py
new file mode 100644
index 0000000..1d40efb
--- /dev/null
+++ b/tests/tcg/multiarch/gdbstub/late-attach.py
@@ -0,0 +1,28 @@
+"""Test attaching GDB to a running process.
+
+SPDX-License-Identifier: GPL-2.0-or-later
+"""
+from test_gdbstub import main, report
+
+
+def run_test():
+ """Run through the tests one by one"""
+ try:
+ phase = gdb.parse_and_eval("phase").string()
+ except gdb.error:
+ # Assume the guest did not reach main().
+ phase = "start"
+
+ if phase == "start":
+ gdb.execute("break sigwait")
+ gdb.execute("continue")
+ phase = gdb.parse_and_eval("phase").string()
+ report(phase == "sigwait", "{} == \"sigwait\"".format(phase))
+
+ gdb.execute("signal SIGUSR1")
+
+ exitcode = int(gdb.parse_and_eval("$_exitcode"))
+ report(exitcode == 0, "{} == 0".format(exitcode))
+
+
+main(run_test)
diff --git a/tests/tcg/multiarch/gdbstub/prot-none.py b/tests/tcg/multiarch/gdbstub/prot-none.py
index 7e26458..51082a3 100644
--- a/tests/tcg/multiarch/gdbstub/prot-none.py
+++ b/tests/tcg/multiarch/gdbstub/prot-none.py
@@ -5,7 +5,7 @@ This runs as a sourced script (via -x, via run-test.py).
SPDX-License-Identifier: GPL-2.0-or-later
"""
import ctypes
-from test_gdbstub import main, report
+from test_gdbstub import gdb_exit, main, report
def probe_proc_self_mem():
@@ -22,7 +22,7 @@ def run_test():
"""Run through the tests one by one"""
if not probe_proc_self_mem():
print("SKIP: /proc/self/mem is not usable")
- exit(0)
+ gdb_exit(0)
gdb.Breakpoint("break_here")
gdb.execute("continue")
val = gdb.parse_and_eval("*(char[2] *)q").string()
diff --git a/tests/tcg/multiarch/gdbstub/test-proc-mappings.py b/tests/tcg/multiarch/gdbstub/test-proc-mappings.py
index 564613f..6eb6ebf 100644
--- a/tests/tcg/multiarch/gdbstub/test-proc-mappings.py
+++ b/tests/tcg/multiarch/gdbstub/test-proc-mappings.py
@@ -3,22 +3,17 @@
This runs as a sourced script (via -x, via run-test.py)."""
from __future__ import print_function
import gdb
-from test_gdbstub import main, report
+from test_gdbstub import gdb_exit, main, report
def run_test():
"""Run through the tests one by one"""
- try:
- mappings = gdb.execute("info proc mappings", False, True)
- except gdb.error as exc:
- exc_str = str(exc)
- if "Not supported on this target." in exc_str:
- # Detect failures due to an outstanding issue with how GDB handles
- # the x86_64 QEMU's target.xml, which does not contain the
- # definition of orig_rax. Skip the test in this case.
- print("SKIP: {}".format(exc_str))
- return
- raise
+ if gdb.selected_inferior().architecture().name() == "m68k":
+ # m68k GDB supports only GDB_OSABI_SVR4, but GDB_OSABI_LINUX is
+ # required for the info proc support (see set_gdbarch_info_proc()).
+ print("SKIP: m68k GDB does not support GDB_OSABI_LINUX")
+ gdb_exit(0)
+ mappings = gdb.execute("info proc mappings", False, True)
report(isinstance(mappings, str), "Fetched the mappings from the inferior")
# Broken with host page size > guest page size
# report("/sha1" in mappings, "Found the test binary name in the mappings")
diff --git a/tests/tcg/multiarch/late-attach.c b/tests/tcg/multiarch/late-attach.c
new file mode 100644
index 0000000..20a3640
--- /dev/null
+++ b/tests/tcg/multiarch/late-attach.c
@@ -0,0 +1,41 @@
+/*
+ * Test attaching GDB to a running process.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static const char *phase = "start";
+
+int main(void)
+{
+ sigset_t set;
+ int sig;
+
+ assert(sigfillset(&set) == 0);
+ assert(sigprocmask(SIG_BLOCK, &set, NULL) == 0);
+
+ /* Let GDB know it can send SIGUSR1. */
+ phase = "sigwait";
+ if (getenv("LATE_ATTACH_PY")) {
+ assert(sigwait(&set, &sig) == 0);
+ if (sig != SIGUSR1) {
+ fprintf(stderr, "Unexpected signal %d\n", sig);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Check that the guest does not see host_interrupt_signal. */
+ assert(sigpending(&set) == 0);
+ for (sig = 1; sig < NSIG; sig++) {
+ if (sigismember(&set, sig)) {
+ fprintf(stderr, "Unexpected signal %d\n", sig);
+ return EXIT_FAILURE;
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/multiarch/linux/linux-sigrtminmax.c b/tests/tcg/multiarch/linux/linux-sigrtminmax.c
new file mode 100644
index 0000000..a7059aa
--- /dev/null
+++ b/tests/tcg/multiarch/linux/linux-sigrtminmax.c
@@ -0,0 +1,74 @@
+/*
+ * Test the lowest and the highest real-time signals.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* For hexagon and microblaze. */
+#ifndef __SIGRTMIN
+#define __SIGRTMIN 32
+#endif
+
+extern char **environ;
+
+static bool seen_sigrtmin, seen_sigrtmax;
+
+static void handle_signal(int sig)
+{
+ if (sig == SIGRTMIN) {
+ seen_sigrtmin = true;
+ } else if (sig == SIGRTMAX) {
+ seen_sigrtmax = true;
+ } else {
+ _exit(1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char *qemu = getenv("QEMU");
+ struct sigaction act;
+
+ assert(qemu);
+
+ if (!getenv("QEMU_RTSIG_MAP")) {
+ char **new_argv = malloc((argc + 2) + sizeof(char *));
+ int tsig1, hsig1, count1, tsig2, hsig2, count2;
+ char rt_sigmap[64];
+
+ /* Re-exec with a mapping that includes SIGRTMIN and SIGRTMAX. */
+ new_argv[0] = qemu;
+ memcpy(&new_argv[1], argv, (argc + 1) * sizeof(char *));
+ tsig1 = __SIGRTMIN;
+ /* The host must have a few signals starting from this one. */
+ hsig1 = 36;
+ count1 = SIGRTMIN - __SIGRTMIN + 1;
+ tsig2 = SIGRTMAX;
+ hsig2 = hsig1 + count1;
+ count2 = 1;
+ snprintf(rt_sigmap, sizeof(rt_sigmap), "%d %d %d,%d %d %d",
+ tsig1, hsig1, count1, tsig2, hsig2, count2);
+ setenv("QEMU_RTSIG_MAP", rt_sigmap, 0);
+ assert(execve(new_argv[0], new_argv, environ) == 0);
+ return EXIT_FAILURE;
+ }
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = handle_signal;
+ assert(sigaction(SIGRTMIN, &act, NULL) == 0);
+ assert(sigaction(SIGRTMAX, &act, NULL) == 0);
+
+ assert(kill(getpid(), SIGRTMIN) == 0);
+ assert(seen_sigrtmin);
+ assert(kill(getpid(), SIGRTMAX) == 0);
+ assert(seen_sigrtmax);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/multiarch/test-vma.c b/tests/tcg/multiarch/linux/test-vma.c
index 2893d60..2893d60 100644
--- a/tests/tcg/multiarch/test-vma.c
+++ b/tests/tcg/multiarch/linux/test-vma.c
diff --git a/tests/tcg/multiarch/sigreturn-sigmask.c b/tests/tcg/multiarch/sigreturn-sigmask.c
new file mode 100644
index 0000000..e6cc904
--- /dev/null
+++ b/tests/tcg/multiarch/sigreturn-sigmask.c
@@ -0,0 +1,51 @@
+/*
+ * Test that sigreturn() does not corrupt the signal mask.
+ * Block SIGUSR2 and handle SIGUSR1.
+ * Then sigwait() SIGUSR2, which relies on it remaining blocked.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int seen_sig = -1;
+
+static void signal_func(int sig)
+{
+ seen_sig = sig;
+}
+
+static void *thread_func(void *arg)
+{
+ kill(getpid(), SIGUSR2);
+ return NULL;
+}
+
+int main(void)
+{
+ struct sigaction act = {
+ .sa_handler = signal_func,
+ };
+ pthread_t thread;
+ sigset_t set;
+ int sig;
+
+ assert(sigaction(SIGUSR1, &act, NULL) == 0);
+
+ assert(sigemptyset(&set) == 0);
+ assert(sigaddset(&set, SIGUSR2) == 0);
+ assert(sigprocmask(SIG_BLOCK, &set, NULL) == 0);
+
+ kill(getpid(), SIGUSR1);
+ assert(seen_sig == SIGUSR1);
+
+ assert(pthread_create(&thread, NULL, thread_func, NULL) == 0);
+ assert(sigwait(&set, &sig) == 0);
+ assert(sig == SIGUSR2);
+ assert(pthread_join(thread, NULL) == 0);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/multiarch/system/Makefile.softmmu-target b/tests/tcg/multiarch/system/Makefile.softmmu-target
index 32dc0f9..07be001 100644
--- a/tests/tcg/multiarch/system/Makefile.softmmu-target
+++ b/tests/tcg/multiarch/system/Makefile.softmmu-target
@@ -65,3 +65,9 @@ endif
MULTIARCH_RUNS += run-gdbstub-memory run-gdbstub-interrupt \
run-gdbstub-untimely-packet run-gdbstub-registers
+
+# Test plugin memory access instrumentation
+run-plugin-memory-with-libmem.so: \
+ PLUGIN_ARGS=$(COMMA)region-summary=true
+run-plugin-memory-with-libmem.so: \
+ CHECK_PLUGIN_OUTPUT_COMMAND=$(MULTIARCH_SYSTEM_SRC)/validate-memory-counts.py $@.out
diff --git a/tests/tcg/multiarch/system/memory.c b/tests/tcg/multiarch/system/memory.c
index 6eb2eb1..7508f6b 100644
--- a/tests/tcg/multiarch/system/memory.c
+++ b/tests/tcg/multiarch/system/memory.c
@@ -20,20 +20,28 @@
# error "Target does not specify CHECK_UNALIGNED"
#endif
+uint32_t test_read_count;
+uint32_t test_write_count;
+
#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)))
+__attribute__((aligned(TEST_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)
+static void pdot(int count, bool write)
{
+ if (write) {
+ test_write_count++;
+ } else {
+ test_read_count++;
+ }
if (count % 128 == 0) {
ml_printf(".");
}
@@ -63,12 +71,14 @@ static void init_test_data_u8(int unused_offset)
int i;
(void)(unused_offset);
- ml_printf("Filling test area with u8:");
+ ml_printf("Filling test area with u8 (%p):", ptr);
+
for (i = 0; i < TEST_SIZE; i++) {
*ptr++ = BYTE_NEXT(count);
- pdot(i);
+ pdot(i, true);
}
- ml_printf("done\n");
+
+ ml_printf("done %d @ %p\n", i, ptr);
}
/*
@@ -91,10 +101,11 @@ static void init_test_data_s8(bool neg_first)
neg_first ? "neg first" : "pos first");
for (i = 0; i < TEST_SIZE / 2; i++) {
*ptr++ = get_byte(i, neg_first);
+ pdot(i, true);
*ptr++ = get_byte(i, !neg_first);
- pdot(i);
+ pdot(i, true);
}
- ml_printf("done\n");
+ ml_printf("done %d @ %p\n", i * 2, ptr);
}
/*
@@ -105,9 +116,19 @@ static void reset_start_data(int offset)
{
uint32_t *ptr = (uint32_t *) &test_data[0];
int i;
+
+ if (!offset) {
+ return;
+ }
+
+ ml_printf("Flushing %d bytes from %p: ", offset, ptr);
+
for (i = 0; i < offset; i++) {
*ptr++ = 0;
+ pdot(i, true);
}
+
+ ml_printf("done %d @ %p\n", i, ptr);
}
static void init_test_data_u16(int offset)
@@ -117,17 +138,17 @@ static void init_test_data_u16(int 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);
+ ml_printf("Filling test area with u16 (offset %d, %p):", offset, ptr);
+
for (i = 0; i < max; i++) {
uint16_t low = BYTE_NEXT(count), high = BYTE_NEXT(count);
word = BYTE_SHIFT(high, 1) | BYTE_SHIFT(low, 0);
*ptr++ = word;
- pdot(i);
+ pdot(i, true);
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
}
static void init_test_data_u32(int offset)
@@ -137,21 +158,22 @@ static void init_test_data_u32(int 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);
+ ml_printf("Filling test area with u32 (offset %d, %p):", offset, ptr);
+
for (i = 0; i < max; i++) {
uint32_t b4 = BYTE_NEXT(count), b3 = BYTE_NEXT(count);
uint32_t b2 = BYTE_NEXT(count), b1 = BYTE_NEXT(count);
word = BYTE_SHIFT(b1, 3) | BYTE_SHIFT(b2, 2) | BYTE_SHIFT(b3, 1) |
BYTE_SHIFT(b4, 0);
*ptr++ = word;
- pdot(i);
+ pdot(i, true);
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
}
+#if __SIZEOF_POINTER__ >= 8
static void init_test_data_u64(int offset)
{
uint8_t count = 0;
@@ -159,10 +181,10 @@ static void init_test_data_u64(int 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);
+ ml_printf("Filling test area with u64 (offset %d, %p):", offset, ptr);
+
for (i = 0; i < max; i++) {
uint64_t b8 = BYTE_NEXT(count), b7 = BYTE_NEXT(count);
uint64_t b6 = BYTE_NEXT(count), b5 = BYTE_NEXT(count);
@@ -172,10 +194,11 @@ static void init_test_data_u64(int offset)
BYTE_SHIFT(b4, 4) | BYTE_SHIFT(b5, 3) | BYTE_SHIFT(b6, 2) |
BYTE_SHIFT(b7, 1) | BYTE_SHIFT(b8, 0);
*ptr++ = word;
- pdot(i);
+ pdot(i, true);
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
}
+#endif
static bool read_test_data_u16(int offset)
{
@@ -194,11 +217,11 @@ static bool read_test_data_u16(int offset)
ml_printf("Error %d < %d\n", high, low);
return false;
} else {
- pdot(i);
+ pdot(i, false);
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
return true;
}
@@ -236,13 +259,14 @@ static bool read_test_data_u32(int offset)
ml_printf("Error %d, %d, %d, %d", b1, b2, b3, b4);
return false;
} else {
- pdot(i);
+ pdot(i, false);
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
return true;
}
+#if __SIZEOF_POINTER__ >= 8
static bool read_test_data_u64(int offset)
{
uint64_t word, *ptr = (uint64_t *)&test_data[offset];
@@ -290,17 +314,22 @@ static bool read_test_data_u64(int offset)
b1, b2, b3, b4, b5, b6, b7, b8);
return false;
} else {
- pdot(i);
+ pdot(i, false);
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
return true;
}
+#endif
/* 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 };
+read_ufn read_ufns[] = {
+ read_test_data_u16,
+ read_test_data_u32,
+#if __SIZEOF_POINTER__ >= 8
+ read_test_data_u64
+#endif
+};
bool do_unsigned_reads(int start_off)
{
@@ -357,15 +386,17 @@ static bool read_test_data_s8(int offset, bool neg_first)
second = *ptr++;
if (neg_first && first < 0 && second > 0) {
- pdot(i);
+ pdot(i, false);
+ pdot(i, false);
} else if (!neg_first && first > 0 && second < 0) {
- pdot(i);
+ pdot(i, false);
+ pdot(i, false);
} else {
ml_printf("Error %d %c %d\n", first, neg_first ? '<' : '>', second);
return false;
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i * 2, ptr);
return true;
}
@@ -390,15 +421,15 @@ static bool read_test_data_s16(int offset, bool neg_first)
int32_t data = *ptr++;
if (neg_first && data < 0) {
- pdot(i);
+ pdot(i, false);
} else if (!neg_first && data > 0) {
- pdot(i);
+ pdot(i, false);
} else {
ml_printf("Error %d %c 0\n", data, neg_first ? '<' : '>');
return false;
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
return true;
}
@@ -423,15 +454,15 @@ static bool read_test_data_s32(int offset, bool neg_first)
int64_t data = *ptr++;
if (neg_first && data < 0) {
- pdot(i);
+ pdot(i, false);
} else if (!neg_first && data > 0) {
- pdot(i);
+ pdot(i, false);
} else {
ml_printf("Error %d %c 0\n", data, neg_first ? '<' : '>');
return false;
}
}
- ml_printf("done @ %p\n", ptr);
+ ml_printf("done %d @ %p\n", i, ptr);
return true;
}
@@ -465,16 +496,23 @@ bool do_signed_reads(bool neg_first)
return ok;
}
-init_ufn init_ufns[] = { init_test_data_u8,
- init_test_data_u16,
- init_test_data_u32,
- init_test_data_u64 };
+init_ufn init_ufns[] = {
+ init_test_data_u8,
+ init_test_data_u16,
+ init_test_data_u32,
+#if __SIZEOF_POINTER__ >= 8
+ init_test_data_u64
+#endif
+};
int main(void)
{
int i;
bool ok = true;
+ ml_printf("Test data start: 0x%lx\n", (unsigned long)&test_data[0]);
+ ml_printf("Test data end: 0x%lx\n", (unsigned long)&test_data[TEST_SIZE]);
+
/* Run through the unsigned tests first */
for (i = 0; i < ARRAY_SIZE(init_ufns) && ok; i++) {
ok = do_unsigned_test(init_ufns[i]);
@@ -490,6 +528,8 @@ int main(void)
ok = do_signed_reads(true);
}
+ ml_printf("Test data read: %lu\n", (unsigned long)test_read_count);
+ ml_printf("Test data write: %lu\n", (unsigned long)test_write_count);
ml_printf("Test complete: %s\n", ok ? "PASSED" : "FAILED");
return ok ? 0 : -1;
}
diff --git a/tests/tcg/multiarch/system/validate-memory-counts.py b/tests/tcg/multiarch/system/validate-memory-counts.py
new file mode 100755
index 0000000..5b8bbf3
--- /dev/null
+++ b/tests/tcg/multiarch/system/validate-memory-counts.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python3
+#
+# validate-memory-counts.py: check we instrumented memory properly
+#
+# This program takes two inputs:
+# - the mem plugin output
+# - the memory binary output
+#
+# Copyright (C) 2024 Linaro Ltd
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import sys
+from argparse import ArgumentParser
+
+def extract_counts(path):
+ """
+ Load the output from path and extract the lines containing:
+
+ Test data start: 0x40214000
+ Test data end: 0x40218001
+ Test data read: 2522280
+ Test data write: 262111
+
+ From the stream of data. Extract the values for use in the
+ validation function.
+ """
+ start_address = None
+ end_address = None
+ read_count = 0
+ write_count = 0
+ with open(path, 'r') as f:
+ for line in f:
+ if line.startswith("Test data start:"):
+ start_address = int(line.split(':')[1].strip(), 16)
+ elif line.startswith("Test data end:"):
+ end_address = int(line.split(':')[1].strip(), 16)
+ elif line.startswith("Test data read:"):
+ read_count = int(line.split(':')[1].strip())
+ elif line.startswith("Test data write:"):
+ write_count = int(line.split(':')[1].strip())
+ return start_address, end_address, read_count, write_count
+
+
+def parse_plugin_output(path, start, end):
+ """
+ Load the plugin output from path in the form of:
+
+ Region Base, Reads, Writes, Seen all
+ 0x0000000040004000, 31093, 0, false
+ 0x0000000040214000, 2522280, 278579, true
+ 0x0000000040000000, 137398, 0, false
+ 0x0000000040210000, 54727397, 33721956, false
+
+ And extract the ranges that match test data start and end and
+ return the results.
+ """
+ total_reads = 0
+ total_writes = 0
+ seen_all = False
+
+ with open(path, 'r') as f:
+ next(f) # Skip the header
+ for line in f:
+
+ if line.startswith("Region Base"):
+ continue
+
+ parts = line.strip().split(', ')
+ if len(parts) != 4:
+ continue
+
+ region_base = int(parts[0], 16)
+ reads = int(parts[1])
+ writes = int(parts[2])
+
+ if start <= region_base < end: # Checking if within range
+ total_reads += reads
+ total_writes += writes
+ seen_all = parts[3] == "true"
+
+ return total_reads, total_writes, seen_all
+
+def main() -> None:
+ """
+ Process the arguments, injest the program and plugin out and
+ verify they match up and report if they do not.
+ """
+ parser = ArgumentParser(description="Validate memory instrumentation")
+ parser.add_argument('test_output',
+ help="The output from the test itself")
+ parser.add_argument('plugin_output',
+ help="The output from memory plugin")
+ parser.add_argument('--bss-cleared',
+ action='store_true',
+ help='Assume bss was cleared (and adjusts counts).')
+
+ args = parser.parse_args()
+
+ # Extract counts from memory binary
+ start, end, exp_reads, exp_writes = extract_counts(args.test_output)
+
+ # Some targets clear BSS before running but the test doesn't know
+ # that so we adjust it by the size of the test region.
+ if args.bss_cleared:
+ exp_writes += 16384
+
+ if start is None or end is None:
+ print("Failed to test_data boundaries from output.")
+ sys.exit(1)
+
+ # Parse plugin output
+ preads, pwrites, seen_all = parse_plugin_output(args.plugin_output,
+ start, end)
+
+ if not seen_all:
+ print("Fail: didn't instrument all accesses to test_data.")
+ sys.exit(1)
+
+ # Compare and report
+ if preads == exp_reads and pwrites == exp_writes:
+ sys.exit(0)
+ else:
+ print("Fail: The memory reads and writes count does not match.")
+ print(f"Expected Reads: {exp_reads}, Actual Reads: {preads}")
+ print(f"Expected Writes: {exp_writes}, Actual Writes: {pwrites}")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/tcg/multiarch/test-plugin-mem-access.c b/tests/tcg/multiarch/test-plugin-mem-access.c
new file mode 100644
index 0000000..057b9aa
--- /dev/null
+++ b/tests/tcg/multiarch/test-plugin-mem-access.c
@@ -0,0 +1,177 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Check if we detect all memory accesses expected using plugin API.
+ * Used in conjunction with ./check-plugin-mem-access.sh check script.
+ * Output of this program is the list of patterns expected in plugin output.
+ *
+ * 8,16,32 load/store are tested for all arch.
+ * 64,128 load/store are tested for aarch64/x64.
+ * atomic operations (8,16,32,64) are tested for x64 only.
+ */
+
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(__x86_64__)
+#include <emmintrin.h>
+#elif defined(__aarch64__)
+#include <arm_neon.h>
+#endif /* __x86_64__ */
+
+static void *data;
+
+/* ,store_u8,.*,8,store,0xf1 */
+#define PRINT_EXPECTED(function, type, value, action) \
+do { \
+ printf(",%s,.*,%d,%s,%s\n", \
+ #function, (int) sizeof(type) * 8, action, value); \
+} \
+while (0)
+
+#define DEFINE_STORE(name, type, value) \
+ \
+static void print_expected_store_##name(void) \
+{ \
+ PRINT_EXPECTED(store_##name, type, #value, "store"); \
+} \
+ \
+static void store_##name(void) \
+{ \
+ *((type *)data) = value; \
+ print_expected_store_##name(); \
+}
+
+#define DEFINE_ATOMIC_OP(name, type, value) \
+ \
+static void print_expected_atomic_op_##name(void) \
+{ \
+ PRINT_EXPECTED(atomic_op_##name, type, "0x0*42", "load"); \
+ PRINT_EXPECTED(atomic_op_##name, type, #value, "store"); \
+} \
+ \
+static void atomic_op_##name(void) \
+{ \
+ *((type *)data) = 0x42; \
+ __sync_val_compare_and_swap((type *)data, 0x42, value); \
+ print_expected_atomic_op_##name(); \
+}
+
+#define DEFINE_LOAD(name, type, value) \
+ \
+static void print_expected_load_##name(void) \
+{ \
+ PRINT_EXPECTED(load_##name, type, #value, "load"); \
+} \
+ \
+static void load_##name(void) \
+{ \
+ \
+ /* volatile forces load to be generated. */ \
+ volatile type src = *((type *) data); \
+ volatile type dest = src; \
+ (void)src, (void)dest; \
+ print_expected_load_##name(); \
+}
+
+DEFINE_STORE(u8, uint8_t, 0xf1)
+DEFINE_LOAD(u8, uint8_t, 0xf1)
+DEFINE_STORE(u16, uint16_t, 0xf123)
+DEFINE_LOAD(u16, uint16_t, 0xf123)
+DEFINE_STORE(u32, uint32_t, 0xff112233)
+DEFINE_LOAD(u32, uint32_t, 0xff112233)
+
+#if defined(__x86_64__) || defined(__aarch64__)
+DEFINE_STORE(u64, uint64_t, 0xf123456789abcdef)
+DEFINE_LOAD(u64, uint64_t, 0xf123456789abcdef)
+
+static void print_expected_store_u128(void)
+{
+ PRINT_EXPECTED(store_u128, __int128,
+ "0xf122334455667788f123456789abcdef", "store");
+}
+
+static void store_u128(void)
+{
+#ifdef __x86_64__
+ _mm_store_si128(data, _mm_set_epi32(0xf1223344, 0x55667788,
+ 0xf1234567, 0x89abcdef));
+#else
+ const uint32_t init[4] = {0x89abcdef, 0xf1234567, 0x55667788, 0xf1223344};
+ uint32x4_t vec = vld1q_u32(init);
+ vst1q_u32(data, vec);
+#endif /* __x86_64__ */
+ print_expected_store_u128();
+}
+
+static void print_expected_load_u128(void)
+{
+ PRINT_EXPECTED(load_u128, __int128,
+ "0xf122334455667788f123456789abcdef", "load");
+}
+
+static void load_u128(void)
+{
+#ifdef __x86_64__
+ __m128i var = _mm_load_si128(data);
+#else
+ uint32x4_t var = vld1q_u32(data);
+#endif
+ (void) var;
+ print_expected_load_u128();
+}
+#endif /* __x86_64__ || __aarch64__ */
+
+#if defined(__x86_64__)
+DEFINE_ATOMIC_OP(u8, uint8_t, 0xf1)
+DEFINE_ATOMIC_OP(u16, uint16_t, 0xf123)
+DEFINE_ATOMIC_OP(u32, uint32_t, 0xff112233)
+DEFINE_ATOMIC_OP(u64, uint64_t, 0xf123456789abcdef)
+#endif /* __x86_64__ */
+
+static void *f(void *p)
+{
+ return NULL;
+}
+
+int main(void)
+{
+ /*
+ * We force creation of a second thread to enable cpu flag CF_PARALLEL.
+ * This will generate atomic operations when needed.
+ */
+ pthread_t thread;
+ pthread_create(&thread, NULL, &f, NULL);
+ pthread_join(thread, NULL);
+
+ /* allocate storage up to 128 bits */
+ data = malloc(16);
+
+ store_u8();
+ load_u8();
+
+ store_u16();
+ load_u16();
+
+ store_u32();
+ load_u32();
+
+#if defined(__x86_64__) || defined(__aarch64__)
+ store_u64();
+ load_u64();
+
+ store_u128();
+ load_u128();
+#endif /* __x86_64__ || __aarch64__ */
+
+#if defined(__x86_64__)
+ atomic_op_u8();
+ atomic_op_u16();
+ atomic_op_u32();
+ atomic_op_u64();
+#endif /* __x86_64__ */
+
+ free(data);
+}