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 #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 #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 */ diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h index 11f0487731..bd4d084bcb 100644 --- a/tests/checkasm/checkasm.h +++ b/tests/checkasm/checkasm.h @@ -23,7 +23,6 @@ #ifndef TESTS_CHECKASM_CHECKASM_H #define TESTS_CHECKASM_CHECKASM_H -#include #include #include "config.h" @@ -43,6 +42,37 @@ #include "libavutil/lfg.h" #include "libavutil/timer.h" +#ifdef _WIN32 +#include +#if ARCH_X86_32 +#include +typedef jmp_buf checkasm_context; +#define checkasm_save_context() checkasm_handle_signal(setjmp(checkasm_context_buf)) +#define checkasm_load_context(s) longjmp(checkasm_context_buf, s) +#elif WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +/* setjmp/longjmp on Windows on architectures using SEH (all except x86_32) + * will try to use SEH to unwind the stack, which doesn't work for assembly + * functions without unwind information. */ +typedef struct { CONTEXT c; int status; } checkasm_context; +#define checkasm_save_context() \ + (checkasm_context_buf.status = 0, \ + RtlCaptureContext(&checkasm_context_buf.c), \ + checkasm_handle_signal(checkasm_context_buf.status)) +#define checkasm_load_context(s) \ + (checkasm_context_buf.status = s, \ + RtlRestoreContext(&checkasm_context_buf.c, NULL)) +#else +#define checkasm_context void* +#define checkasm_save_context() 0 +#define checkasm_load_context() do {} while (0) +#endif +#else +#include +typedef sigjmp_buf checkasm_context; +#define checkasm_save_context() checkasm_handle_signal(sigsetjmp(checkasm_context_buf, 1)) +#define checkasm_load_context(s) siglongjmp(checkasm_context_buf, s) +#endif + void checkasm_check_aacencdsp(void); void checkasm_check_aacpsdsp(void); void checkasm_check_ac3dsp(void); @@ -106,9 +136,11 @@ struct CheckasmPerf; void *checkasm_check_func(void *func, const char *name, ...) av_printf_format(2, 3); int checkasm_bench_func(void); void checkasm_fail_func(const char *msg, ...) av_printf_format(1, 2); -void checkasm_fail_signal(int signum); struct CheckasmPerf *checkasm_get_perf_context(void); void checkasm_report(const char *name, ...) av_printf_format(1, 2); +void checkasm_set_signal_handler_state(int enabled); +int checkasm_handle_signal(int s); +extern checkasm_context checkasm_context_buf; /* float compare utilities */ int float_near_ulp(float a, float b, unsigned max_ulp); @@ -132,7 +164,7 @@ static av_unused void *func_ref, *func_new; #define BENCH_RUNS 1000 /* Trade-off between accuracy and speed */ /* Decide whether or not the specified function needs to be tested */ -#define check_func(func, ...) (func_ref = checkasm_check_func((func_new = func), __VA_ARGS__)) +#define check_func(func, ...) (checkasm_save_context(), func_ref = checkasm_check_func((func_new = func), __VA_ARGS__)) /* Declare the function prototype. The first argument is the return value, the remaining * arguments are the function parameters. Naming parameters is optional. */ @@ -147,7 +179,10 @@ static av_unused void *func_ref, *func_new; #define report checkasm_report /* Call the reference function */ -#define call_ref(...) ((func_type *)func_ref)(__VA_ARGS__) +#define call_ref(...)\ + (checkasm_set_signal_handler_state(1),\ + ((func_type *)func_ref)(__VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #if ARCH_X86 && HAVE_X86ASM /* Verifies that clobbered callee-saved registers are properly saved and restored @@ -180,16 +215,21 @@ void checkasm_stack_clobber(uint64_t clobber, ...); ((cpu_flags) & av_get_cpu_flags()) ? (void *)checkasm_checked_call_emms : \ (void *)checkasm_checked_call; #define CLOB (UINT64_C(0xdeadbeefdeadbeef)) -#define call_new(...) (checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ +#define call_new(...) (checkasm_set_signal_handler_state(1),\ + checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB),\ - checked_call(func_new, 0, 0, 0, 0, 0, __VA_ARGS__)) + checked_call(func_new, 0, 0, 0, 0, 0, __VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #elif ARCH_X86_32 #define declare_new(ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = (void *)checkasm_checked_call; #define declare_new_float(ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = (void *)checkasm_checked_call_float; #define declare_new_emms(cpu_flags, ret, ...) ret (*checked_call)(void *, __VA_ARGS__) = \ ((cpu_flags) & av_get_cpu_flags()) ? (void *)checkasm_checked_call_emms : \ (void *)checkasm_checked_call; -#define call_new(...) checked_call(func_new, __VA_ARGS__) +#define call_new(...)\ + (checkasm_set_signal_handler_state(1),\ + checked_call(func_new, __VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #endif #elif ARCH_ARM && HAVE_ARMV5TE_EXTERNAL /* Use a dummy argument, to offset the real parameters by 2, not only 1. @@ -201,7 +241,10 @@ extern void (*checkasm_checked_call)(void *func, int dummy, ...); #define declare_new(ret, ...) ret (*checked_call)(void *, int dummy, __VA_ARGS__, \ int, int, int, int, int, int, int, int, \ int, int, int, int, int, int, int) = (void *)checkasm_checked_call; -#define call_new(...) checked_call(func_new, 0, __VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0) +#define call_new(...) \ + (checkasm_set_signal_handler_state(1),\ + checked_call(func_new, 0, __VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0));\ + checkasm_set_signal_handler_state(0) #elif ARCH_AARCH64 && !defined(__APPLE__) void checkasm_stack_clobber(uint64_t clobber, ...); void checkasm_checked_call(void *func, ...); @@ -210,35 +253,39 @@ void checkasm_checked_call(void *func, ...); int, int, int, int, int, int, int)\ = (void *)checkasm_checked_call; #define CLOB (UINT64_C(0xdeadbeefdeadbeef)) -#define call_new(...) (checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ +#define call_new(...) (checkasm_set_signal_handler_state(1),\ + checkasm_stack_clobber(CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,\ CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB,CLOB),\ checked_call(func_new, 0, 0, 0, 0, 0, 0, 0, __VA_ARGS__,\ - 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0)) + 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0));\ + checkasm_set_signal_handler_state(0) #elif ARCH_RISCV -void checkasm_set_function(void *, sigjmp_buf); +void checkasm_set_function(void *); void *checkasm_get_wrapper(void); -void checkasm_handle_signal(int signum); #if HAVE_RV && (__riscv_xlen == 64) && defined (__riscv_d) #define declare_new(ret, ...) \ - int checked_call_signum = 0; \ - sigjmp_buf checked_call_jb; \ ret (*checked_call)(__VA_ARGS__) = checkasm_get_wrapper(); #define call_new(...) \ - (checkasm_set_function(func_new, checked_call_jb), \ - (checked_call_signum = sigsetjmp(checked_call_jb, 1)) == 0 \ - ? checked_call(__VA_ARGS__) \ - : (checkasm_fail_signal(checked_call_signum), 0)) + (checkasm_set_signal_handler_state(1),\ + checkasm_set_function(func_new), checked_call(__VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #else #define declare_new(ret, ...) -#define call_new(...) ((func_type *)func_new)(__VA_ARGS__) +#define call_new(...)\ + (checkasm_set_signal_handler_state(1),\ + ((func_type *)func_new)(__VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #endif #else #define declare_new(ret, ...) #define declare_new_float(ret, ...) #define declare_new_emms(cpu_flags, ret, ...) /* Call the function */ -#define call_new(...) ((func_type *)func_new)(__VA_ARGS__) +#define call_new(...)\ + (checkasm_set_signal_handler_state(1),\ + ((func_type *)func_new)(__VA_ARGS__));\ + checkasm_set_signal_handler_state(0) #endif #ifndef declare_new_emms @@ -285,6 +332,7 @@ typedef struct CheckasmPerf { uint64_t tsum = 0;\ int ti, tcount = 0;\ uint64_t t = 0; \ + checkasm_set_signal_handler_state(1);\ for (ti = 0; ti < BENCH_RUNS; ti++) {\ PERF_START(t);\ tfunc(__VA_ARGS__);\ @@ -300,6 +348,7 @@ typedef struct CheckasmPerf { emms_c();\ perf->cycles += t;\ perf->iterations++;\ + checkasm_set_signal_handler_state(0);\ }\ } while (0) #else diff --git a/tests/checkasm/riscv/checkasm.S b/tests/checkasm/riscv/checkasm.S index 971d881157..73ca85f344 100644 --- a/tests/checkasm/riscv/checkasm.S +++ b/tests/checkasm/riscv/checkasm.S @@ -41,7 +41,6 @@ endconst checked_func: .quad 0 - .quad 0 saved_regs: /* Space to spill RA, SP, GP, TP, S0-S11 and FS0-FS11 */ @@ -53,7 +52,6 @@ func checkasm_set_function la.tls.ie t0, checked_func add t0, tp, t0 sd a0, (t0) - sd a1, 8(t0) ret endfunc @@ -177,14 +175,4 @@ func checkasm_get_wrapper, v call checkasm_fail_func j 4b endfunc - -func checkasm_handle_signal - mv a1, a0 - la.tls.ie a0, checked_func - add a0, tp, a0 - ld a0, 8(a0) - beqz a0, 8f - tail siglongjmp -8: tail abort /* No jump buffer to go to */ -endfunc #endif