Add a few basic signal handling tests for user emulation.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
---
tests/tcg/riscv64/Makefile.target | 5 +
tests/tcg/riscv64/test-signal-handling.c | 303 +++++++++++++++++++++++
2 files changed, 308 insertions(+)
create mode 100644 tests/tcg/riscv64/test-signal-handling.c
diff --git a/tests/tcg/riscv64/Makefile.target b/tests/tcg/riscv64/Makefile.target
index 4da5b9a3b3..f318891396 100644
--- a/tests/tcg/riscv64/Makefile.target
+++ b/tests/tcg/riscv64/Makefile.target
@@ -18,3 +18,8 @@ TESTS += test-fcvtmod
test-fcvtmod: CFLAGS += -march=rv64imafdc
test-fcvtmod: LDFLAGS += -static
run-test-fcvtmod: QEMU_OPTS += -cpu rv64,d=true,zfa=true
+
+# Test signal handling.
+TESTS += test-signal-handling
+test-signal-handling: CFLAGS += -march=rv64gc
+run-test-signal-handling: QEMU_OPTS += -cpu rv64
diff --git a/tests/tcg/riscv64/test-signal-handling.c b/tests/tcg/riscv64/test-signal-handling.c
new file mode 100644
index 0000000000..c202503382
--- /dev/null
+++ b/tests/tcg/riscv64/test-signal-handling.c
@@ -0,0 +1,303 @@
+/*
+ * Test for linux-user signal handling.
+ *
+ * This ensures that integer and fp register values are
+ * saved as expected in the sigcontext, created by a SIGILL.
+ *
+ * TODO: Register restore is not explicitly verified, except
+ * for advancing pc, and the restoring of registers that were
+ * clobbered by the compiler in the signal handler.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <execinfo.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <ucontext.h>
+#include <asm/sigcontext.h>
+
+/*
+ * This horrible hack seems to be required when including
+ * signal.h and asm/sigcontext.h, to prevent sigcontext
+ * redefinition by bits/sigcontext.h :(
+ *
+ * bits/sigcontext.h does not have the extended state or
+ * RISCV_V_MAGIC, etc. It could have just been introduced
+ * as a new type.
+ */
+#define _BITS_SIGCONTEXT_H 1
+#include <signal.h>
+
+static uint64_t *initial_gvalues;
+static uint64_t *final_gvalues;
+static uint64_t *signal_gvalues;
+static double *initial_fvalues;
+static double *final_fvalues;
+static double *signal_fvalues;
+
+extern unsigned long unimp_addr[];
+
+static bool got_signal = false;
+
+#define BT_BUF_SIZE 100
+
+static void *find_callchain_root(void)
+{
+ int nptrs;
+ void *buffer[BT_BUF_SIZE];
+
+ nptrs = backtrace(buffer, BT_BUF_SIZE);
+
+ return buffer[nptrs - 1];
+}
+
+static void *callchain_root;
+
+static void ILL_handler(int signo, siginfo_t *info, void *context)
+{
+ ucontext_t *uc = context;
+ struct sigcontext *sc = (struct sigcontext *)&uc->uc_mcontext;
+
+ got_signal = true;
+
+ assert(unimp_addr == info->si_addr);
+ assert(sc->sc_regs.pc == (unsigned long)info->si_addr);
+
+ /* Ensure stack unwind through the signal frame is not broken */
+ assert(callchain_root == find_callchain_root());
+
+ for (int i = 0; i < 31; i++) {
+ ((uint64_t *)signal_gvalues)[i] = ((unsigned long *)&sc->sc_regs.ra)[i];
+ }
+
+ for (int i = 0; i < 32; i++) {
+ ((uint64_t *)signal_fvalues)[i] = sc->sc_fpregs.d.f[i];
+ }
+ /* Test sc->sc_fpregs.d.fcsr ? */
+
+ sc->sc_regs.pc += 4;
+}
+
+static void init_test(void)
+{
+ int i;
+
+ callchain_root = find_callchain_root();
+
+ initial_gvalues = malloc(8 * 31);
+ memset(initial_gvalues, 0, 8 * 31);
+ final_gvalues = malloc(8 * 31);
+ memset(final_gvalues, 0, 8 * 31);
+ signal_gvalues = malloc(8 * 31);
+ memset(signal_gvalues, 0, 8 * 31);
+
+ initial_fvalues = malloc(8 * 32);
+ memset(initial_fvalues, 0, 8 * 32);
+ for (i = 0; i < 32 ; i++) {
+ initial_fvalues[i] = 3.142 * (i + 1);
+ }
+ final_fvalues = malloc(8 * 32);
+ memset(final_fvalues, 0, 8 * 32);
+ signal_fvalues = malloc(8 * 32);
+ memset(signal_fvalues, 0, 8 * 32);
+}
+
+static void run_test(void)
+{
+ asm volatile(
+ /* Save initial values from gp registers */
+ "mv t0, %[initial_gvalues]\n\t"
+ "sd x1, 0x0(t0)\n\t"
+ "sd x2, 0x8(t0)\n\t"
+ "sd x3, 0x10(t0)\n\t"
+ "sd x4, 0x18(t0)\n\t"
+ "sd x5, 0x20(t0)\n\t"
+ "sd x6, 0x28(t0)\n\t"
+ "sd x7, 0x30(t0)\n\t"
+ "sd x8, 0x38(t0)\n\t"
+ "sd x9, 0x40(t0)\n\t"
+ "sd x10, 0x48(t0)\n\t"
+ "sd x11, 0x50(t0)\n\t"
+ "sd x12, 0x58(t0)\n\t"
+ "sd x13, 0x60(t0)\n\t"
+ "sd x14, 0x68(t0)\n\t"
+ "sd x15, 0x70(t0)\n\t"
+ "sd x16, 0x78(t0)\n\t"
+ "sd x17, 0x80(t0)\n\t"
+ "sd x18, 0x88(t0)\n\t"
+ "sd x19, 0x90(t0)\n\t"
+ "sd x20, 0x98(t0)\n\t"
+ "sd x21, 0xa0(t0)\n\t"
+ "sd x22, 0xa8(t0)\n\t"
+ "sd x23, 0xb0(t0)\n\t"
+ "sd x24, 0xb8(t0)\n\t"
+ "sd x25, 0xc0(t0)\n\t"
+ "sd x26, 0xc8(t0)\n\t"
+ "sd x27, 0xd0(t0)\n\t"
+ "sd x28, 0xd8(t0)\n\t"
+ "sd x29, 0xe0(t0)\n\t"
+ "sd x30, 0xe8(t0)\n\t"
+ "sd x31, 0xf0(t0)\n\t"
+ /* Load initial values into float registers */
+ "mv t0, %[initial_fvalues]\n\t"
+ "fld f0, 0x0(t0)\n\t"
+ "fld f1, 0x8(t0)\n\t"
+ "fld f2, 0x10(t0)\n\t"
+ "fld f3, 0x18(t0)\n\t"
+ "fld f4, 0x20(t0)\n\t"
+ "fld f5, 0x28(t0)\n\t"
+ "fld f6, 0x30(t0)\n\t"
+ "fld f7, 0x38(t0)\n\t"
+ "fld f8, 0x40(t0)\n\t"
+ "fld f9, 0x48(t0)\n\t"
+ "fld f10, 0x50(t0)\n\t"
+ "fld f11, 0x58(t0)\n\t"
+ "fld f12, 0x60(t0)\n\t"
+ "fld f13, 0x68(t0)\n\t"
+ "fld f14, 0x70(t0)\n\t"
+ "fld f15, 0x78(t0)\n\t"
+ "fld f16, 0x80(t0)\n\t"
+ "fld f17, 0x88(t0)\n\t"
+ "fld f18, 0x90(t0)\n\t"
+ "fld f19, 0x98(t0)\n\t"
+ "fld f20, 0xa0(t0)\n\t"
+ "fld f21, 0xa8(t0)\n\t"
+ "fld f22, 0xb0(t0)\n\t"
+ "fld f23, 0xb8(t0)\n\t"
+ "fld f24, 0xc0(t0)\n\t"
+ "fld f25, 0xc8(t0)\n\t"
+ "fld f26, 0xd0(t0)\n\t"
+ "fld f27, 0xd8(t0)\n\t"
+ "fld f28, 0xe0(t0)\n\t"
+ "fld f29, 0xe8(t0)\n\t"
+ "fld f30, 0xf0(t0)\n\t"
+ "fld f31, 0xf8(t0)\n\t"
+ /* Trigger the SIGILL */
+".global unimp_addr\n\t"
+"unimp_addr:\n\t"
+ "unimp\n\t"
+ "nop\n\t"
+ /* Save final values from gp registers */
+ "mv t0, %[final_gvalues]\n\t"
+ "sd x1, 0x0(t0)\n\t"
+ "sd x2, 0x8(t0)\n\t"
+ "sd x3, 0x10(t0)\n\t"
+ "sd x4, 0x18(t0)\n\t"
+ "sd x5, 0x20(t0)\n\t"
+ "sd x6, 0x28(t0)\n\t"
+ "sd x7, 0x30(t0)\n\t"
+ "sd x8, 0x38(t0)\n\t"
+ "sd x9, 0x40(t0)\n\t"
+ "sd x10, 0x48(t0)\n\t"
+ "sd x11, 0x50(t0)\n\t"
+ "sd x12, 0x58(t0)\n\t"
+ "sd x13, 0x60(t0)\n\t"
+ "sd x14, 0x68(t0)\n\t"
+ "sd x15, 0x70(t0)\n\t"
+ "sd x16, 0x78(t0)\n\t"
+ "sd x17, 0x80(t0)\n\t"
+ "sd x18, 0x88(t0)\n\t"
+ "sd x19, 0x90(t0)\n\t"
+ "sd x20, 0x98(t0)\n\t"
+ "sd x21, 0xa0(t0)\n\t"
+ "sd x22, 0xa8(t0)\n\t"
+ "sd x23, 0xb0(t0)\n\t"
+ "sd x24, 0xb8(t0)\n\t"
+ "sd x25, 0xc0(t0)\n\t"
+ "sd x26, 0xc8(t0)\n\t"
+ "sd x27, 0xd0(t0)\n\t"
+ "sd x28, 0xd8(t0)\n\t"
+ "sd x29, 0xe0(t0)\n\t"
+ "sd x30, 0xe8(t0)\n\t"
+ "sd x31, 0xf0(t0)\n\t"
+ /* Save final values from float registers */
+ "mv t0, %[final_fvalues]\n\t"
+ "fsd f0, 0x0(t0)\n\t"
+ "fsd f1, 0x8(t0)\n\t"
+ "fsd f2, 0x10(t0)\n\t"
+ "fsd f3, 0x18(t0)\n\t"
+ "fsd f4, 0x20(t0)\n\t"
+ "fsd f5, 0x28(t0)\n\t"
+ "fsd f6, 0x30(t0)\n\t"
+ "fsd f7, 0x38(t0)\n\t"
+ "fsd f8, 0x40(t0)\n\t"
+ "fsd f9, 0x48(t0)\n\t"
+ "fsd f10, 0x50(t0)\n\t"
+ "fsd f11, 0x58(t0)\n\t"
+ "fsd f12, 0x60(t0)\n\t"
+ "fsd f13, 0x68(t0)\n\t"
+ "fsd f14, 0x70(t0)\n\t"
+ "fsd f15, 0x78(t0)\n\t"
+ "fsd f16, 0x80(t0)\n\t"
+ "fsd f17, 0x88(t0)\n\t"
+ "fsd f18, 0x90(t0)\n\t"
+ "fsd f19, 0x98(t0)\n\t"
+ "fsd f20, 0xa0(t0)\n\t"
+ "fsd f21, 0xa8(t0)\n\t"
+ "fsd f22, 0xb0(t0)\n\t"
+ "fsd f23, 0xb8(t0)\n\t"
+ "fsd f24, 0xc0(t0)\n\t"
+ "fsd f25, 0xc8(t0)\n\t"
+ "fsd f26, 0xd0(t0)\n\t"
+ "fsd f27, 0xd8(t0)\n\t"
+ "fsd f28, 0xe0(t0)\n\t"
+ "fsd f29, 0xe8(t0)\n\t"
+ "fsd f30, 0xf0(t0)\n\t"
+ "fsd f31, 0xf8(t0)\n\t"
+ : "=m" (initial_gvalues),
+ "=m" (final_gvalues),
+ "=m" (final_fvalues)
+ : "m" (initial_fvalues),
+ [initial_gvalues] "r" (initial_gvalues),
+ [initial_fvalues] "r" (initial_fvalues),
+ [final_gvalues] "r" (final_gvalues),
+ [final_fvalues] "r" (final_fvalues)
+ : "t0",
+ "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
+ "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
+ "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
+ "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31");
+
+ assert(got_signal);
+
+ /*
+ * x4 / t0 is used in the asm so it has to be handled specially
+ * and is not a simple equality.
+ */
+ assert(initial_gvalues[4] == (unsigned long)initial_gvalues);
+ assert(signal_gvalues[4] == (unsigned long)initial_fvalues);
+ assert(final_gvalues[4] == (unsigned long)final_gvalues);
+ initial_gvalues[4] = final_gvalues[4] = signal_gvalues[4] = 0;
+
+ /*
+ * Ensure registers match before, inside, and after signal
+ * handler.
+ */
+ assert(!memcmp(initial_gvalues, final_gvalues, 8 * 31));
+ assert(!memcmp(initial_gvalues, signal_gvalues, 8 * 31));
+ assert(!memcmp(initial_fvalues, final_fvalues, 8 * 32));
+ assert(!memcmp(initial_fvalues, signal_fvalues, 8 * 32));
+}
+
+int main(void)
+{
+ struct sigaction act = { 0 };
+
+ act.sa_flags = SA_SIGINFO;
+ act.sa_sigaction = &ILL_handler;
+ if (sigaction(SIGILL, &act, NULL) == -1) {
+ perror("sigaction");
+ exit(EXIT_FAILURE);
+ }
+
+ init_test();
+
+ run_test();
+}
--
2.51.0
© 2016 - 2026 Red Hat, Inc.