aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/arm64/signal/test_signals_utils.h
blob: 762c8fe9c54adc2c3ea5540a9fa94a63839a2759 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/* 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