| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* Copyright (C) 2019 ARM Limited */ |
| |
| #ifndef __TEST_SIGNALS_UTILS_H__ |
| #define __TEST_SIGNALS_UTILS_H__ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <linux/compiler.h> |
| |
| #include "test_signals.h" |
| |
| int test_init(struct tdescr *td); |
| int test_setup(struct tdescr *td); |
| void test_cleanup(struct tdescr *td); |
| int test_run(struct tdescr *td); |
| void test_result(struct tdescr *td); |
| |
| static inline bool feats_ok(struct tdescr *td) |
| { |
| if (td->feats_incompatible & td->feats_supported) |
| return false; |
| return (td->feats_required & td->feats_supported) == td->feats_required; |
| } |
| |
| /* |
| * Obtaining a valid and full-blown ucontext_t from userspace is tricky: |
| * libc getcontext does() not save all the regs and messes with some of |
| * them (pstate value in particular is not reliable). |
| * |
| * Here we use a service signal to grab the ucontext_t from inside a |
| * dedicated signal handler, since there, it is populated by Kernel |
| * itself in setup_sigframe(). The grabbed context is then stored and |
| * made available in td->live_uc. |
| * |
| * As service-signal is used a SIGTRAP induced by a 'brk' instruction, |
| * because here we have to avoid syscalls to trigger the signal since |
| * they would cause any SVE sigframe content (if any) to be removed. |
| * |
| * Anyway this function really serves a dual purpose: |
| * |
| * 1. grab a valid sigcontext into td->live_uc for result analysis: in |
| * such case it returns 1. |
| * |
| * 2. detect if, somehow, a previously grabbed live_uc context has been |
| * used actively with a sigreturn: in such a case the execution would have |
| * magically resumed in the middle of this function itself (seen_already==1): |
| * in such a case return 0, since in fact we have not just simply grabbed |
| * the context. |
| * |
| * This latter case is useful to detect when a fake_sigreturn test-case has |
| * unexpectedly survived without hitting a SEGV. |
| * |
| * Note that the case of runtime dynamically sized sigframes (like in SVE |
| * context) is still NOT addressed: sigframe size is supposed to be fixed |
| * at sizeof(ucontext_t). |
| */ |
| static __always_inline bool get_current_context(struct tdescr *td, |
| ucontext_t *dest_uc, |
| size_t dest_sz) |
| { |
| static volatile bool seen_already; |
| int i; |
| char *uc = (char *)dest_uc; |
| |
| assert(td && dest_uc); |
| /* it's a genuine invocation..reinit */ |
| seen_already = 0; |
| td->live_uc_valid = 0; |
| td->live_sz = dest_sz; |
| |
| /* |
| * This is a memset() but we don't want the compiler to |
| * optimise it into either instructions or a library call |
| * which might be incompatible with streaming mode. |
| */ |
| for (i = 0; i < td->live_sz; i++) { |
| uc[i] = 0; |
| OPTIMIZER_HIDE_VAR(uc[0]); |
| } |
| |
| td->live_uc = dest_uc; |
| /* |
| * Grab ucontext_t triggering a SIGTRAP. |
| * |
| * Note that: |
| * - live_uc_valid is declared volatile sig_atomic_t in |
| * struct tdescr since it will be changed inside the |
| * sig_copyctx handler |
| * - the additional 'memory' clobber is there to avoid possible |
| * compiler's assumption on live_uc_valid and the content |
| * pointed by dest_uc, which are all changed inside the signal |
| * handler |
| * - BRK causes a debug exception which is handled by the Kernel |
| * and finally causes the SIGTRAP signal to be delivered to this |
| * test thread. Since such delivery happens on the ret_to_user() |
| * /do_notify_resume() debug exception return-path, we are sure |
| * that the registered SIGTRAP handler has been run to completion |
| * before the execution path is restored here: as a consequence |
| * we can be sure that the volatile sig_atomic_t live_uc_valid |
| * carries a meaningful result. Being in a single thread context |
| * we'll also be sure that any access to memory modified by the |
| * handler (namely ucontext_t) will be visible once returned. |
| * - note that since we are using a breakpoint instruction here |
| * to cause a SIGTRAP, the ucontext_t grabbed from the signal |
| * handler would naturally contain a PC pointing exactly to this |
| * BRK line, which means that, on return from the signal handler, |
| * or if we place the ucontext_t on the stack to fake a sigreturn, |
| * we'll end up in an infinite loop of BRK-SIGTRAP-handler. |
| * For this reason we take care to artificially move forward the |
| * PC to the next instruction while inside the signal handler. |
| */ |
| asm volatile ("brk #666" |
| : "+m" (*dest_uc) |
| : |
| : "memory"); |
| |
| /* |
| * If we were grabbing a streaming mode context then we may |
| * have entered streaming mode behind the system's back and |
| * libc or compiler generated code might decide to do |
| * something invalid in streaming mode, or potentially even |
| * the state of ZA. Issue a SMSTOP to exit both now we have |
| * grabbed the state. |
| */ |
| if (td->feats_supported & FEAT_SME) |
| asm volatile("msr S0_3_C4_C6_3, xzr"); |
| |
| /* |
| * If we get here with seen_already==1 it implies the td->live_uc |
| * context has been used to get back here....this probably means |
| * a test has failed to cause a SEGV...anyway live_uc does not |
| * point to a just acquired copy of ucontext_t...so return 0 |
| */ |
| if (seen_already) { |
| fprintf(stdout, |
| "Unexpected successful sigreturn detected: live_uc is stale !\n"); |
| return 0; |
| } |
| seen_already = 1; |
| |
| return td->live_uc_valid; |
| } |
| |
| int fake_sigreturn(void *sigframe, size_t sz, int misalign_bytes); |
| #endif |