From 41aaeb1ff0f0c72ae44fd05328d0ff45fc0d1a73 Mon Sep 17 00:00:00 2001 From: Tony Wasserka Date: Thu, 22 Sep 2022 10:03:37 +0200 Subject: [PATCH] FEXLinuxTests: Migrate signal tests to Catch2 --- unittests/FEXLinuxTests/Disabled_Tests | 10 - unittests/FEXLinuxTests/Expected_Output | 8 - unittests/FEXLinuxTests/Known_Failures | 24 +-- .../tests/signal/invalid_hlt.cpp | 40 ++-- .../tests/signal/invalid_int.cpp | 42 ++-- .../tests/signal/invalid_int1.cpp | 40 ++-- .../tests/signal/invalid_int3.cpp | 41 ++-- .../tests/signal/invalid_ud2.cpp | 40 ++-- .../FEXLinuxTests/tests/signal/invalid_util.h | 26 +++ .../tests/signal/pthread_cancel.cpp | 22 +-- .../tests/signal/sigtest_no_defer.cpp | 31 +-- .../tests/signal/sigtest_samask.cpp | 23 +-- .../tests/signal/sigtest_sigmask.cpp | 19 +- .../tests/signal/synchronous-signal-block.cpp | 186 +++++++++++++----- .../tests/signal/timer-sigev-thread.cpp | 24 ++- 15 files changed, 305 insertions(+), 271 deletions(-) create mode 100644 unittests/FEXLinuxTests/tests/signal/invalid_util.h diff --git a/unittests/FEXLinuxTests/Disabled_Tests b/unittests/FEXLinuxTests/Disabled_Tests index 903110f78..b0c214ad2 100644 --- a/unittests/FEXLinuxTests/Disabled_Tests +++ b/unittests/FEXLinuxTests/Disabled_Tests @@ -5,13 +5,3 @@ # These sometimes crash FEX with SIGSEGV timer-sigev-thread.32 timer-sigev-thread.64 - -# These fail on arm because of sigbus handling -synchronous-signal-block-sbus.32 -synchronous-signal-block-sbus.64 -synchronous-signal-block-abus.32 -synchronous-signal-block-abus.64 - -# These fail on arm because we don't raise FPE -synchronous-signal-block-sfpe.32 -synchronous-signal-block-sfpe.64 \ No newline at end of file diff --git a/unittests/FEXLinuxTests/Expected_Output b/unittests/FEXLinuxTests/Expected_Output index da747ccef..e69de29bb 100644 --- a/unittests/FEXLinuxTests/Expected_Output +++ b/unittests/FEXLinuxTests/Expected_Output @@ -1,8 +0,0 @@ -synchronous-signal-block-ssegv.32 -11 -synchronous-signal-block-ssegv.64 -11 -synchronous-signal-block-sill.32 -4 -synchronous-signal-block-sill.64 -4 -synchronous-signal-block-sbus.32 -7 -synchronous-signal-block-sbus.64 -7 -synchronous-signal-block-sfpe.32 -8 -synchronous-signal-block-sfpe.64 -8 \ No newline at end of file diff --git a/unittests/FEXLinuxTests/Known_Failures b/unittests/FEXLinuxTests/Known_Failures index 203ed21f3..cc6ec373d 100644 --- a/unittests/FEXLinuxTests/Known_Failures +++ b/unittests/FEXLinuxTests/Known_Failures @@ -6,15 +6,9 @@ timer-sigev-thread.32 timer-sigev-thread.64 -# These fail on arm because of sigbus handling -synchronous-signal-block-sbus.32 -synchronous-signal-block-sbus.64 -synchronous-signal-block-abus.32 -synchronous-signal-block-abus.64 - -# These fail on arm because we don't raise FPE -synchronous-signal-block-sfpe.32 -synchronous-signal-block-sfpe.64 +# These trigger various quirks in FEX's signal handling +synchronous-signal-block.32 +synchronous-signal-block.64 ### ### Failing Tests ### @@ -25,15 +19,3 @@ sigtest_samask.32 sigtest_samask.64 sigtest_sigmask.32 sigtest_sigmask.64 - -# These fail to do default signal catching behaviour -synchronous-signal-block-ssegv.32 -synchronous-signal-block-ssegv.64 -synchronous-signal-block-sill.32 -synchronous-signal-block-sill.64 - -# These fail to queue the signals -synchronous-signal-block-asegv.32 -synchronous-signal-block-asegv.64 -synchronous-signal-block-aill.32 -synchronous-signal-block-aill.64 \ No newline at end of file diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_hlt.cpp b/unittests/FEXLinuxTests/tests/signal/invalid_hlt.cpp index 32f434b05..d8ef0d0c7 100644 --- a/unittests/FEXLinuxTests/tests/signal/invalid_hlt.cpp +++ b/unittests/FEXLinuxTests/tests/signal/invalid_hlt.cpp @@ -1,3 +1,7 @@ +#include "invalid_util.h" + +#include + #include #include #include @@ -9,7 +13,7 @@ __attribute__((naked, nocf_check)) static void InvalidINT() { __asm volatile(R"( hlt; - ret; # Just incase it gets past the int + ret; )"); } @@ -19,31 +23,25 @@ constexpr int EXPECTED_ERR = 0; constexpr int EXPECTED_SI_CODE = 128; constexpr int EXPECTED_SIGNAL = SIGSEGV; -static void handler(int signal, siginfo_t *siginfo, void* context) { - ucontext_t* _context = (ucontext_t*)context; -#ifndef REG_RIP -#define REG_RIP REG_EIP -#endif - if (_context->uc_mcontext.gregs[REG_RIP] == EXPECTED_RIP && - _context->uc_mcontext.gregs[REG_TRAPNO] == EXPECTED_TRAPNO && - _context->uc_mcontext.gregs[REG_ERR] == EXPECTED_ERR && - siginfo->si_code == EXPECTED_SI_CODE && - signal == EXPECTED_SIGNAL) { - exit(0); - } - else { - exit(1); - } -} - -int main() { +TEST_CASE("Signals: Invalid HLT") { + capturing_handler_skip = 1; struct sigaction act{}; - act.sa_sigaction = handler; + act.sa_sigaction = CapturingHandler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, nullptr); sigaction(SIGTRAP, &act, nullptr); sigaction(SIGILL, &act, nullptr); InvalidINT(); - return 1; + +#ifndef REG_RIP +#define REG_RIP REG_EIP +#endif + + REQUIRE(from_handler.has_value()); + CHECK(from_handler->mctx.gregs[REG_RIP] == EXPECTED_RIP); + CHECK(from_handler->mctx.gregs[REG_TRAPNO] == EXPECTED_TRAPNO); + CHECK(from_handler->mctx.gregs[REG_ERR] == EXPECTED_ERR); + CHECK(from_handler->si_code == EXPECTED_SI_CODE); + CHECK(from_handler->signal == EXPECTED_SIGNAL); } diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_int.cpp b/unittests/FEXLinuxTests/tests/signal/invalid_int.cpp index 71a28681f..e377277b8 100644 --- a/unittests/FEXLinuxTests/tests/signal/invalid_int.cpp +++ b/unittests/FEXLinuxTests/tests/signal/invalid_int.cpp @@ -1,3 +1,7 @@ +#include "invalid_util.h" + +#include + #include #include #include @@ -9,7 +13,7 @@ __attribute__((naked, nocf_check)) static void InvalidINT() { __asm volatile(R"( int $0x2d; - ret; # Just incase it gets past the int + ret; )"); } @@ -19,33 +23,25 @@ constexpr int EXPECTED_ERR = 362; constexpr int EXPECTED_SI_CODE = 128; constexpr int EXPECTED_SIGNAL = SIGSEGV; - -static void handler(int signal, siginfo_t *siginfo, void* context) { - ucontext_t* _context = (ucontext_t*)context; -#ifndef REG_RIP -#define REG_RIP REG_EIP -#endif - - if (_context->uc_mcontext.gregs[REG_RIP] == EXPECTED_RIP && - _context->uc_mcontext.gregs[REG_TRAPNO] == EXPECTED_TRAPNO && - _context->uc_mcontext.gregs[REG_ERR] == EXPECTED_ERR && - siginfo->si_code == EXPECTED_SI_CODE && - signal == EXPECTED_SIGNAL) { - exit(0); - } - else { - exit(1); - } -} - -int main() { +TEST_CASE("Signals: Invalid INT") { + capturing_handler_skip = 2; struct sigaction act{}; - act.sa_sigaction = handler; + act.sa_sigaction = CapturingHandler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, nullptr); sigaction(SIGTRAP, &act, nullptr); sigaction(SIGILL, &act, nullptr); InvalidINT(); - return 1; + +#ifndef REG_RIP +#define REG_RIP REG_EIP +#endif + + REQUIRE(from_handler.has_value()); + CHECK(from_handler->mctx.gregs[REG_RIP] == EXPECTED_RIP); + CHECK(from_handler->mctx.gregs[REG_TRAPNO] == EXPECTED_TRAPNO); + CHECK(from_handler->mctx.gregs[REG_ERR] == EXPECTED_ERR); + CHECK(from_handler->si_code == EXPECTED_SI_CODE); + CHECK(from_handler->signal == EXPECTED_SIGNAL); } diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_int1.cpp b/unittests/FEXLinuxTests/tests/signal/invalid_int1.cpp index c6ec7a59b..74682207a 100644 --- a/unittests/FEXLinuxTests/tests/signal/invalid_int1.cpp +++ b/unittests/FEXLinuxTests/tests/signal/invalid_int1.cpp @@ -1,3 +1,7 @@ +#include "invalid_util.h" + +#include + #include #include #include @@ -9,7 +13,7 @@ __attribute__((naked, nocf_check)) static void InvalidINT() { __asm volatile(R"( .byte 0xF1; # int1 - ret; # Just incase it gets past the int + ret; )"); } @@ -19,31 +23,25 @@ constexpr int EXPECTED_ERR = 0; constexpr int EXPECTED_SI_CODE = 1; constexpr int EXPECTED_SIGNAL = SIGTRAP; -static void handler(int signal, siginfo_t *siginfo, void* context) { - ucontext_t* _context = (ucontext_t*)context; -#ifndef REG_RIP -#define REG_RIP REG_EIP -#endif - if (_context->uc_mcontext.gregs[REG_RIP] == EXPECTED_RIP && - _context->uc_mcontext.gregs[REG_TRAPNO] == EXPECTED_TRAPNO && - _context->uc_mcontext.gregs[REG_ERR] == EXPECTED_ERR && - siginfo->si_code == EXPECTED_SI_CODE && - signal == EXPECTED_SIGNAL) { - exit(0); - } - else { - exit(1); - } -} - -int main() { +TEST_CASE("Signals: Invalid INT1") { + capturing_handler_skip = 0; struct sigaction act{}; - act.sa_sigaction = handler; + act.sa_sigaction = CapturingHandler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, nullptr); sigaction(SIGTRAP, &act, nullptr); sigaction(SIGILL, &act, nullptr); InvalidINT(); - return 1; + +#ifndef REG_RIP +#define REG_RIP REG_EIP +#endif + +REQUIRE(from_handler.has_value()); +CHECK(from_handler->mctx.gregs[REG_RIP] == EXPECTED_RIP); +CHECK(from_handler->mctx.gregs[REG_TRAPNO] == EXPECTED_TRAPNO); +CHECK(from_handler->mctx.gregs[REG_ERR] == EXPECTED_ERR); +CHECK(from_handler->si_code == EXPECTED_SI_CODE); +CHECK(from_handler->signal == EXPECTED_SIGNAL); } diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_int3.cpp b/unittests/FEXLinuxTests/tests/signal/invalid_int3.cpp index f185f6450..41a0ffaa9 100644 --- a/unittests/FEXLinuxTests/tests/signal/invalid_int3.cpp +++ b/unittests/FEXLinuxTests/tests/signal/invalid_int3.cpp @@ -1,3 +1,7 @@ +#include "invalid_util.h" + +#include + #include #include #include @@ -9,7 +13,7 @@ __attribute__((naked, nocf_check)) static void InvalidINT() { __asm volatile(R"( int3; - ret; # Just incase it gets past the int + ret; )"); } @@ -19,32 +23,25 @@ constexpr int EXPECTED_ERR = 0; constexpr int EXPECTED_SI_CODE = 128; constexpr int EXPECTED_SIGNAL = SIGTRAP; -static void handler(int signal, siginfo_t *siginfo, void* context) { - ucontext_t* _context = (ucontext_t*)context; -#ifndef REG_RIP -#define REG_RIP REG_EIP -#endif - - if (_context->uc_mcontext.gregs[REG_RIP] == EXPECTED_RIP && - _context->uc_mcontext.gregs[REG_TRAPNO] == EXPECTED_TRAPNO && - _context->uc_mcontext.gregs[REG_ERR] == EXPECTED_ERR && - siginfo->si_code == EXPECTED_SI_CODE && - signal == EXPECTED_SIGNAL) { - exit(0); - } - else { - exit(1); - } -} - -int main() { +TEST_CASE("Signals: Invalid INT3") { + capturing_handler_skip = 0; struct sigaction act{}; - act.sa_sigaction = handler; + act.sa_sigaction = CapturingHandler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, nullptr); sigaction(SIGTRAP, &act, nullptr); sigaction(SIGILL, &act, nullptr); InvalidINT(); - return 1; + +#ifndef REG_RIP +#define REG_RIP REG_EIP +#endif + + REQUIRE(from_handler.has_value()); + CHECK(from_handler->mctx.gregs[REG_RIP] == EXPECTED_RIP); + CHECK(from_handler->mctx.gregs[REG_TRAPNO] == EXPECTED_TRAPNO); + CHECK(from_handler->mctx.gregs[REG_ERR] == EXPECTED_ERR); + CHECK(from_handler->si_code == EXPECTED_SI_CODE); + CHECK(from_handler->signal == EXPECTED_SIGNAL); } diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_ud2.cpp b/unittests/FEXLinuxTests/tests/signal/invalid_ud2.cpp index 827db2b63..a6bfedd0e 100644 --- a/unittests/FEXLinuxTests/tests/signal/invalid_ud2.cpp +++ b/unittests/FEXLinuxTests/tests/signal/invalid_ud2.cpp @@ -1,3 +1,7 @@ +#include "invalid_util.h" + +#include + #include #include #include @@ -9,7 +13,7 @@ __attribute__((naked, nocf_check)) static void InvalidINT() { __asm volatile(R"( ud2; - ret; # Just incase it gets past the int + ret; )"); } @@ -19,31 +23,25 @@ constexpr int EXPECTED_ERR = 0; constexpr int EXPECTED_SI_CODE = 2; constexpr int EXPECTED_SIGNAL = SIGILL; -static void handler(int signal, siginfo_t *siginfo, void* context) { - ucontext_t* _context = (ucontext_t*)context; -#ifndef REG_RIP -#define REG_RIP REG_EIP -#endif - if (_context->uc_mcontext.gregs[REG_RIP] == EXPECTED_RIP && - _context->uc_mcontext.gregs[REG_TRAPNO] == EXPECTED_TRAPNO && - _context->uc_mcontext.gregs[REG_ERR] == EXPECTED_ERR && - siginfo->si_code == EXPECTED_SI_CODE && - signal == EXPECTED_SIGNAL) { - exit(0); - } - else { - exit(1); - } -} - -int main() { +TEST_CASE("Signals: Invalid UD2") { + capturing_handler_skip = 2; struct sigaction act{}; - act.sa_sigaction = handler; + act.sa_sigaction = CapturingHandler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &act, nullptr); sigaction(SIGTRAP, &act, nullptr); sigaction(SIGILL, &act, nullptr); InvalidINT(); - return 1; + +#ifndef REG_RIP +#define REG_RIP REG_EIP +#endif + + REQUIRE(from_handler.has_value()); + CHECK(from_handler->mctx.gregs[REG_RIP] == EXPECTED_RIP); + CHECK(from_handler->mctx.gregs[REG_TRAPNO] == EXPECTED_TRAPNO); + CHECK(from_handler->mctx.gregs[REG_ERR] == EXPECTED_ERR); + CHECK(from_handler->si_code == EXPECTED_SI_CODE); + CHECK(from_handler->signal == EXPECTED_SIGNAL); } diff --git a/unittests/FEXLinuxTests/tests/signal/invalid_util.h b/unittests/FEXLinuxTests/tests/signal/invalid_util.h new file mode 100644 index 000000000..57cf90849 --- /dev/null +++ b/unittests/FEXLinuxTests/tests/signal/invalid_util.h @@ -0,0 +1,26 @@ +#include +#include + +struct CapturedHandlerState { + mcontext_t mctx; + int signal; + int si_code; +}; + +std::optional from_handler; + +// Number of bytes to skip to resume from the signal handler +int capturing_handler_skip = 0; + +// Signal handler that writes its context data to the global from_handler +static void CapturingHandler(int signal, siginfo_t *siginfo, void* context) { + ucontext_t* _context = (ucontext_t*)context; + from_handler = { _context->uc_mcontext, signal, siginfo->si_code }; +#ifdef REG_RIP +#define FEX_IP_REG REG_RIP +#else +#define FEX_IP_REG REG_EIP +#endif + _context->uc_mcontext.gregs[FEX_IP_REG] += capturing_handler_skip; +#undef FEX_IP_REG +} diff --git a/unittests/FEXLinuxTests/tests/signal/pthread_cancel.cpp b/unittests/FEXLinuxTests/tests/signal/pthread_cancel.cpp index 6a07ad8b9..f2a94a550 100644 --- a/unittests/FEXLinuxTests/tests/signal/pthread_cancel.cpp +++ b/unittests/FEXLinuxTests/tests/signal/pthread_cancel.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -59,36 +61,26 @@ static void *thread_func(void *ignored_argument) { return NULL; } -int main(void) { +TEST_CASE("pthreads cancel") { pthread_t thr; void *res; int s; /* Start a thread and then send it a cancellation request. */ - s = pthread_create(&thr, NULL, &thread_func, NULL); - if (s != 0) - handle_error_en(s, "pthread_create"); + REQUIRE(pthread_create(&thr, NULL, &thread_func, NULL) == 0); while (!thread_ready.load()) ; printf("main(): sending cancellation request\n"); - s = pthread_cancel(thr); - if (s != 0) - handle_error_en(s, "pthread_cancel"); + REQUIRE(pthread_cancel(thr) == 0); cancel_sent = true; /* Join with thread to see what its exit status was. */ - s = pthread_join(thr, &res); - if (s != 0) - handle_error_en(s, "pthread_join"); + REQUIRE(pthread_join(thr, &res) == 0); - if (res == PTHREAD_CANCELED) - printf("main(): thread was canceled\n"); - else - printf("main(): thread wasn't canceled (shouldn't happen!)\n"); - exit(EXIT_SUCCESS); + CHECK(res == PTHREAD_CANCELED); } diff --git a/unittests/FEXLinuxTests/tests/signal/sigtest_no_defer.cpp b/unittests/FEXLinuxTests/tests/signal/sigtest_no_defer.cpp index 307fc9813..541c389ed 100644 --- a/unittests/FEXLinuxTests/tests/signal/sigtest_no_defer.cpp +++ b/unittests/FEXLinuxTests/tests/signal/sigtest_no_defer.cpp @@ -1,9 +1,10 @@ +#include + #include #include #include #include -volatile bool loop = false; volatile int count = 0; volatile int count2 = 0; @@ -11,8 +12,6 @@ volatile int count2 = 0; #define SIGN SIGTSTP void sig_handler(int signum, siginfo_t *info, void *context) { - loop = false; - printf("Inside handler function\n"); if (count != 0) { printf("SA_NODEFER bug\n"); @@ -26,35 +25,25 @@ void sig_handler(int signum, siginfo_t *info, void *context) { sigprocmask(0, 0, &old); sigprocmask(SIG_SETMASK, &old, 0); } - + if (count2 < NUMCOUNT) { printf("Nested Raising %d, %d of %d times\n", signum, 1 + count, NUMCOUNT); count2++; raise(signum); count++; } else { - exit(0); - printf("Exiting\n"); + // Return to caller } } -int main() { +TEST_CASE("Signals: No defer") { struct sigaction act = {0}; act.sa_flags = SA_SIGINFO | SA_NODEFER; act.sa_sigaction = &sig_handler; - if (sigaction(SIGN, &act, NULL) != 0) { - printf("sigaction failed\n"); - return -3; - } - loop = true; - while (loop) { - printf("Inside main loop, raising signal\n"); - raise(SIGN); - if (loop) { - printf("Error: Signal did not get raised\n"); - return -3; - } - } - return -2; + REQUIRE(sigaction(SIGN, &act, NULL) == 0); + + raise(SIGN); + CHECK(count == 10); + CHECK(count2 == 10); } diff --git a/unittests/FEXLinuxTests/tests/signal/sigtest_samask.cpp b/unittests/FEXLinuxTests/tests/signal/sigtest_samask.cpp index 4faafc938..f81f3901c 100644 --- a/unittests/FEXLinuxTests/tests/signal/sigtest_samask.cpp +++ b/unittests/FEXLinuxTests/tests/signal/sigtest_samask.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -27,7 +29,7 @@ void sig_handler(int signum) { printf("Signal reentering bug\n"); exit(-1); } - + if (count < NUMCOUNT) { printf("Nested Raising sig%d, %d of %d times\n", signum, 1 + count, NUMCOUNT); count2++; @@ -42,11 +44,8 @@ void sig_handler(int signum) { } } -int main() { - if (signal(SIGN, sig_handler) != 0) { - printf("Signal() failed\n"); - return -2; - } +TEST_CASE("Signals: samask") { + REQUIRE(signal(SIGN, sig_handler) == 0); // test if sigmask blocks during execution as expected last = false; @@ -54,10 +53,7 @@ int main() { while (loop) { printf("Inside main loop, raising signal\n"); raise(SIGN); - if (loop) { - printf("Error: Signal did not get raised\n"); - return -4; - } + REQUIRE_FALSE(loop); } last = true; loop = true; @@ -70,11 +66,6 @@ int main() { while (loop) { printf("Inside last loop, raising signal\n"); raise(SIGN); - if (loop) { - printf("Error: Signal did not get raised\n"); - return -3; - } + REQUIRE_FALSE(loop); } - printf("All good, Exiting\n"); - return 0; } diff --git a/unittests/FEXLinuxTests/tests/signal/sigtest_sigmask.cpp b/unittests/FEXLinuxTests/tests/signal/sigtest_sigmask.cpp index 458cef85e..401cf358b 100644 --- a/unittests/FEXLinuxTests/tests/signal/sigtest_sigmask.cpp +++ b/unittests/FEXLinuxTests/tests/signal/sigtest_sigmask.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -23,26 +25,19 @@ void sig_handler(int signum, siginfo_t *info, void *context) { sigfillset(&uctx->uc_sigmask); } -int main() { +TEST_CASE("Signals: sigmask") { struct sigaction act = {0}; act.sa_flags = SA_SIGINFO; act.sa_sigaction = &sig_handler; - if (sigaction(SIGN, &act, NULL) != 0) { - printf("sigaction() failed\n"); - return -2; - } + REQUIRE(sigaction(SIGN, &act, NULL) == 0); loop = true; while (loop) { printf("Inside main loop, raising signal\n"); raise(SIGN); - if (loop) { - printf("Error: Signal did not get raised\n"); - return -3; - } - } - printf("Exiting\n"); - return 0; + // Ensure the signal got indeed raised + REQUIRE_FALSE(loop); + } } diff --git a/unittests/FEXLinuxTests/tests/signal/synchronous-signal-block.cpp b/unittests/FEXLinuxTests/tests/signal/synchronous-signal-block.cpp index aefe5904e..84444d1c4 100644 --- a/unittests/FEXLinuxTests/tests/signal/synchronous-signal-block.cpp +++ b/unittests/FEXLinuxTests/tests/signal/synchronous-signal-block.cpp @@ -1,73 +1,159 @@ -auto args = "ssegv, asegv, sill, aill, sbus, abus, sfpe, afpe"; +// Triggering synchronous POSIX signals while they're masked triggers a +// process exit. In contrast, if an asynchronous signals is triggered, the +// corresponding signal handler will be invoked once the signal is unmasked. +// +// To test synchronous signals, the test forks and triggers the signal in the +// child process. For asynchronous signals, a signal handler that sets a global +// variable is used. -#include -#include -#include +#include +#include + + +#include +#include +#include #include #include -#include #include +#include +#include -#define handle_error(msg) \ - do { \ - perror(msg); \ - exit(EXIT_FAILURE); \ - } while (0) +#include -char *buffer; -int flag = 0; +static jmp_buf jmpbuf; -static void handler(int sig, siginfo_t *si, void *unused) { +struct HandledSignal { + int signal; + uintptr_t addr; +}; + +static std::optional handled_signal; + +static void handler(int sig, siginfo_t *si, void *context) { printf("Got %d at address: 0x%lx\n", sig, (long)si->si_addr); - exit(1); + handled_signal = { sig, reinterpret_cast(si->si_addr) }; + siglongjmp(jmpbuf, 1); } -int main(int argc, char *argv[]) { - if (argc == 1) { - printf("please specify one of %s\n", args); +// Helper that masks all signals and unmasks them on destruction +struct GuardedSignalMask { + sigset_t oldset {}; + + GuardedSignalMask() { + sigset_t set; + sigfillset(&set); + sigprocmask(SIG_SETMASK, &set, &oldset); } - struct sigaction sa; + ~GuardedSignalMask() { + sigprocmask(SIG_SETMASK, &oldset, nullptr); + } +}; - sa.sa_flags = SA_SIGINFO; - sigemptyset(&sa.sa_mask); - sa.sa_sigaction = handler; - sigaction(SIGSEGV, &sa, NULL); - sigaction(SIGBUS, &sa, NULL); - sigaction(SIGILL, &sa, NULL); - sigaction(SIGFPE, &sa, NULL); +// Checks if the given function causes the process to exit. +// The function is executed in a process fork. +template +std::optional CheckIfExitsFromSignal(F&& f) { + if (fork() == 0) { + GuardedSignalMask guard; + std::forward(f)(); + exit(1); + } else { + int status = 0; + wait(&status); + return status; + } +} +// Checks if the given function causes a signal handler to be invoked +template +std::optional CheckIfSignalHandlerCalled(F&& f) { + handled_signal = {}; + struct sigaction oldsa[4]; + + if (!sigsetjmp(jmpbuf, 1)) { + // Handle all signals by the test handler + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = handler; + sigaction(SIGSEGV, &sa, &oldsa[0]); + sigaction(SIGBUS, &sa, &oldsa[1]); + sigaction(SIGILL, &sa, &oldsa[2]); + sigaction(SIGFPE, &sa, &oldsa[3]); + + // Mask signals and run given callback + GuardedSignalMask guard; + std::forward(f)(); + } + + // Restore previous signal handlers + sigaction(SIGSEGV, &oldsa[0], nullptr); + sigaction(SIGBUS, &oldsa[1], nullptr); + sigaction(SIGILL, &oldsa[2], nullptr); + sigaction(SIGFPE, &oldsa[3], nullptr); + + return handled_signal; +} + +// For ssegv, we fail to do default signal catching behaviour +TEST_CASE("Signals: ssegv") { + auto status = CheckIfExitsFromSignal([]() { *(int*)0x32 = 0x64; }); + REQUIRE(status.has_value()); + CHECK(WIFSIGNALED(*status) == true); + CHECK(WTERMSIG(*status) == SIGSEGV); +} + +// For sill, we fail to do default signal catching behaviour +TEST_CASE("Signals: sill") { + auto status = CheckIfExitsFromSignal([]() { asm volatile("ud2\n"); }); + REQUIRE(status.has_value()); + CHECK(WIFSIGNALED(*status) == true); + CHECK(WTERMSIG(*status) == SIGILL); +} + +// sbus and abus fail on arm because of sigbus handling +TEST_CASE("Signals: sbus") { auto map1 = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); auto map2 = (char *)mremap(map1, 4096, 8192, MREMAP_MAYMOVE); - sigset_t set; - sigfillset(&set); + auto status = CheckIfExitsFromSignal([&]() { map2[4096] = 2; }); + REQUIRE(status.has_value()); + CHECK(WIFSIGNALED(*status) == true); + CHECK(WTERMSIG(*status) == SIGBUS); +} - sigprocmask(SIG_SETMASK, &set, nullptr); - - if (strcmp(argv[1], "ssegv") == 0) { - *(int *)(0x32) = 0x64; - } else if (strcmp(argv[1], "sill") == 0) { - asm volatile("ud2\n"); - } else if (strcmp(argv[1], "sbus") == 0) { - map2[4096] = 2; - } else if (strcmp(argv[1], "sfpe") == 0) { +// sfpe and afpe fail on arm because we don't raise FPE +TEST_CASE("Signals: sfpe") { + auto status = CheckIfExitsFromSignal([&]() { volatile int a = 10; volatile int b = 0; volatile int c = a / b; printf("result: %d\n", c); - } else if (strcmp(argv[1], "asegv") == 0) { - raise(SIGSEGV); - } else if (strcmp(argv[1], "aill") == 0) { - raise(SIGILL); - } else if (strcmp(argv[1], "abus") == 0) { - raise(SIGBUS); - } else if (strcmp(argv[1], "afpe") == 0) { - raise(SIGFPE); - } else { - printf("Invalid argument %s\n", argv[1]); - printf("please specify one of %s\n", args); - } - - exit(0); + }); + REQUIRE(status.has_value()); + CHECK(WIFSIGNALED(*status) == true); + CHECK(WTERMSIG(*status) == SIGFPE); +} + +// These fail to queue the signals +TEST_CASE("Signals: asynchronous") { + int tested_signal = GENERATE(SIGSEGV, SIGILL, SIGBUS, SIGFPE); + + // Check that the signal handler is delayed until unmasking. + bool handled_asynchronously = false; + auto handled_signal = CheckIfSignalHandlerCalled([&]() { + GuardedSignalMask guard{}; + raise(tested_signal); + + // Verify the rest of this function is still executed + handled_asynchronously = true; + + // Destructor of GuardedSignalMask will unmask signals now, + // after which the signal handler should run + }); + REQUIRE(handled_signal.has_value()); + CHECK(handled_signal->signal == tested_signal); + CHECK(handled_asynchronously); } diff --git a/unittests/FEXLinuxTests/tests/signal/timer-sigev-thread.cpp b/unittests/FEXLinuxTests/tests/signal/timer-sigev-thread.cpp index f97979777..469c924f0 100644 --- a/unittests/FEXLinuxTests/tests/signal/timer-sigev-thread.cpp +++ b/unittests/FEXLinuxTests/tests/signal/timer-sigev-thread.cpp @@ -1,25 +1,28 @@ // Simple test of timer_create + SIGEV_THREAD, glibc implements it via SIG32 +#include + #include #include #include #include #include +#include #include #include int test; -void timer_handler(union sigval sv) { - auto ok = sv.sival_ptr == &test; - printf("timer_handler called, ok = %d\n", ok); +std::optional sigval_ack; - exit(ok ? 0 : -1); +void timer_handler(union sigval sv) { + sigval_ack = sv.sival_ptr == &test; + printf("timer_handler called, ok = %d\n", *sigval_ack); } -int main() { - +// These sometimes crash FEX with SIGSEGV +TEST_CASE("timer_create and SIGEV_THREAD", "[!mayfail]") { timer_t timer; sigevent sige; itimerspec spec; @@ -39,9 +42,10 @@ int main() { timer_settime(timer, 0, &spec, NULL); - for (;;) - sleep(1); + while (!sigval_ack) { + usleep(10); + } - assert(false && "should never get here"); - return -2; + REQUIRE(sigval_ack.has_value()); + CHECK(*sigval_ack == true); }