aboutsummaryrefslogtreecommitdiffstats
path: root/tests/checkasm/checkasm.c
diff options
context:
space:
mode:
authorMartin Storsjö <martin@martin.st>2023-12-14 14:57:36 +0200
committerMartin Storsjö <martin@martin.st>2024-01-11 14:48:53 +0200
commit65739691b90012c9d93b2e5e0e89a55d8de9eb7b (patch)
tree85fbbc9153914ca62ba808f20c3894157ef9bf9b /tests/checkasm/checkasm.c
parent5e751dabc5d5e53c80e32e56f154ee5944e8f263 (diff)
downloadffmpeg-65739691b90012c9d93b2e5e0e89a55d8de9eb7b.tar.gz
checkasm: Generalize crash handling
This replaces the riscv specific handling from 7212466e735aa187d82f51dadbce957fe3da77f0 (which essentially is reverted), with a different implementation of the same (plus a bit more), based on the corresponding feature in dav1d's checkasm, supporting both Unix and Windows. See in particular the dav1d commits 0b6ee30eab2400e4f85b735ad29a68a842c34e21, 0421f787ea592fd2cc74c887f20b8dc31393788b, 8501a4b20135f93a4c3b426468e2240e872949c5 and d23e87f7aee26ddcf5f7a2e185112031477599a7, authored by Henrik Gramner. The overall approach compared to the existing implementation for riscv is the same; set up a signal handler, store the state with sigsetjmp, jump out of the crashing function with siglongjmp. The main difference is in what happens when the signal handler is invoked. In the previous implementation, it would resume from right before calling the crashing function, and then skip that call based on the setjmp return value. In the imported implementation from dav1d, we return to right before the check_func() call, which will skip testing the current function (as the pointer is the same as it was before). Other differences are: - Support for other signal handling mechanisms (Windows AddVectoredExceptionHandler) - Using RtlCaptureContext/RtlRestoreContext instead of setjmp/longjmp on Windows with SEH - Only catching signals once per function - if more than one signal is delivered before signal handling is reenabled, any signal is handled as it would without our handler - Not using an arch specific signal handler written in assembly Signed-off-by: Martin Storsjö <martin@martin.st>
Diffstat (limited to 'tests/checkasm/checkasm.c')
-rw-r--r--tests/checkasm/checkasm.c96
1 files changed, 84 insertions, 12 deletions
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 09c961f0c7..994d64e96b 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -42,6 +42,11 @@
#include <io.h>
#endif
+#if defined(_WIN32) && !defined(SIGBUS)
+/* non-standard, use the same value as mingw-w64 */
+#define SIGBUS 10
+#endif
+
#if HAVE_SETCONSOLETEXTATTRIBUTE && HAVE_GETSTDHANDLE
#include <windows.h>
#define COLOR_RED FOREGROUND_RED
@@ -329,6 +334,7 @@ static struct {
const char *cpu_flag_name;
const char *test_name;
int verbose;
+ volatile sig_atomic_t catch_signals;
} state;
/* PRNG state */
@@ -630,6 +636,61 @@ static CheckasmFunc *get_func(CheckasmFunc **root, const char *name)
return f;
}
+checkasm_context checkasm_context_buf;
+
+/* Crash handling: attempt to catch crashes and handle them
+ * gracefully instead of just aborting abruptly. */
+#ifdef _WIN32
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+static LONG NTAPI signal_handler(EXCEPTION_POINTERS *e) {
+ int s;
+
+ if (!state.catch_signals)
+ return EXCEPTION_CONTINUE_SEARCH;
+
+ switch (e->ExceptionRecord->ExceptionCode) {
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+ case EXCEPTION_INT_DIVIDE_BY_ZERO:
+ s = SIGFPE;
+ break;
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ case EXCEPTION_PRIV_INSTRUCTION:
+ s = SIGILL;
+ break;
+ case EXCEPTION_ACCESS_VIOLATION:
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
+ case EXCEPTION_DATATYPE_MISALIGNMENT:
+ case EXCEPTION_STACK_OVERFLOW:
+ s = SIGSEGV;
+ break;
+ case EXCEPTION_IN_PAGE_ERROR:
+ s = SIGBUS;
+ break;
+ default:
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+ state.catch_signals = 0;
+ checkasm_load_context(s);
+ return EXCEPTION_CONTINUE_EXECUTION; /* never reached, but shuts up gcc */
+}
+#endif
+#else
+static void signal_handler(int s);
+
+static const struct sigaction signal_handler_act = {
+ .sa_handler = signal_handler,
+ .sa_flags = SA_RESETHAND,
+};
+
+static void signal_handler(int s) {
+ if (state.catch_signals) {
+ state.catch_signals = 0;
+ sigaction(s, &signal_handler_act, NULL);
+ checkasm_load_context(s);
+ }
+}
+#endif
+
/* Perform tests and benchmarks for the specified cpu flag if supported by the host */
static void check_cpu_flag(const char *name, int flag)
{
@@ -740,18 +801,20 @@ int main(int argc, char *argv[])
unsigned int seed = av_get_random_seed();
int i, ret = 0;
+#ifdef _WIN32
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+ AddVectoredExceptionHandler(0, signal_handler);
+#endif
+#else
+ sigaction(SIGBUS, &signal_handler_act, NULL);
+ sigaction(SIGFPE, &signal_handler_act, NULL);
+ sigaction(SIGILL, &signal_handler_act, NULL);
+ sigaction(SIGSEGV, &signal_handler_act, NULL);
+#endif
#if ARCH_ARM && HAVE_ARMV5TE_EXTERNAL
if (have_vfp(av_get_cpu_flags()) || have_neon(av_get_cpu_flags()))
checkasm_checked_call = checkasm_checked_call_vfp;
#endif
-#if ARCH_RISCV && HAVE_RV
- struct sigaction act = {
- .sa_handler = checkasm_handle_signal,
- .sa_flags = 0,
- };
-
- sigaction(SIGILL, &act, NULL);
-#endif
if (!tests[0].func || !cpus[0].flag) {
fprintf(stderr, "checkasm: no tests to perform\n");
@@ -879,13 +942,22 @@ void checkasm_fail_func(const char *msg, ...)
}
}
-void checkasm_fail_signal(int signum)
-{
+void checkasm_set_signal_handler_state(int enabled) {
+ state.catch_signals = enabled;
+}
+
+int checkasm_handle_signal(int s) {
+ if (s) {
#ifdef __GLIBC__
- checkasm_fail_func("fatal signal %d: %s", signum, strsignal(signum));
+ checkasm_fail_func("fatal signal %d: %s", s, strsignal(s));
#else
- checkasm_fail_func("fatal signal %d", signum);
+ checkasm_fail_func(s == SIGFPE ? "fatal arithmetic error" :
+ s == SIGILL ? "illegal instruction" :
+ s == SIGBUS ? "bus error" :
+ "segmentation fault");
#endif
+ }
+ return s;
}
/* Get the benchmark context of the current function */