xref: /xnu-11215.41.3/tests/restartable_ranges.c (revision 33de042d024d46de5ff4e89f2471de6608e37fa4)
1 #include <darwintest.h>
2 #include <kern/restartable.h>
3 #include <mach/mach.h>
4 #include <mach/task.h>
5 #include <os/atomic_private.h>
6 #include <pthread.h>
7 #include <signal.h>
8 #include <stdbool.h>
9 #include <sys/mman.h>
10 #include <unistd.h>
11 #include <dispatch/dispatch.h>
12 
13 T_GLOBAL_META(
14 	T_META_NAMESPACE("xnu"),
15 	T_META_RADAR_COMPONENT_NAME("xnu"),
16 	T_META_RADAR_COMPONENT_VERSION("all"),
17 	T_META_RUN_CONCURRENTLY(true));
18 
19 extern task_restartable_range_t ranges[2];
20 static int step = 0;
21 
22 extern void restartable_function(int *);
23 
24 #if defined(__x86_64__)
25 __asm__("    .align 4\n"
26         "    .text\n"
27         "    .private_extern _restartable_function\n"
28         "_restartable_function:\n"
29         "    incl   (%rdi)\n"
30         "1:\n"
31         "    pause\n"
32         "    jmp 1b\n"
33         "LExit_restartable_function:\n"
34         "    ret\n");
35 #elif defined(__arm64__)
36 __asm__("    .align 4\n"
37         "    .text\n"
38         "    .private_extern _restartable_function\n"
39         "_restartable_function:\n"
40         "    ldr    x11, [x0]\n"
41         "    add    x11, x11, #1\n"
42         "    str    x11, [x0]\n"
43         "1:\n"
44         "    b 1b\n"
45         "LExit_restartable_function:\n"
46         "    ret\n");
47 #else
48 #define SKIP_TEST 1
49 #endif
50 
51 extern uint64_t __thread_selfid(void);
52 extern void fake_msgSend(void * _Nullable);
53 
54 #if defined(__x86_64__)
55 __asm__("    .align 4\n"
56         "    .text\n"
57         "    .private_extern _fake_msgSend\n"
58         "_fake_msgSend:\n"
59         "    movq   (%rdi), %rax\n"             /* load isa */
60         "1:\n"
61         "    movq   16(%rax), %rcx\n"           /* load buckets */
62         "    movq   (%rcx), %rcx\n"             /* load selector */
63         "LRecover_fake_msgSend:\n"
64         "    jmp    1b\n"
65         "LExit_fake_msgSend:\n"
66         "    ret\n");
67 #elif defined(__arm64__)
68 __asm__("    .align 4\n"
69         "    .text\n"
70         "    .private_extern _fake_msgSend\n"
71         "_fake_msgSend:\n"
72         "    ldr    x16, [x0]\n"                /* load isa */
73         "1:\n"
74 #if __LP64__
75         "    ldr    x11, [x16, #16]\n"          /* load buckets */
76 #else
77         "    ldr    x11, [x16, #8]\n"           /* load buckets */
78 #endif
79         "    ldr    x17, [x11]\n"               /* load selector */
80         "LRecover_fake_msgSend:\n"
81         "    b      1b\n"
82         "LExit_fake_msgSend:\n"
83         "    ret\n");
84 #else
85 #define SKIP_TEST 1
86 #endif
87 
88 #ifndef SKIP_TEST
89 
90 __asm__("    .align 4\n"
91         "    .data\n"
92         "    .private_extern _ranges\n"
93         "_ranges:\n"
94 #if __LP64__
95         "    .quad _restartable_function\n"
96 #else
97         "    .long _restartable_function\n"
98         "    .long 0\n"
99 #endif
100         "    .short LExit_restartable_function - _restartable_function\n"
101         "    .short LExit_restartable_function - _restartable_function\n"
102         "    .long 0\n"
103         "\n"
104 #if __LP64__
105         "    .quad _fake_msgSend\n"
106 #else
107         "    .long _fake_msgSend\n"
108         "    .long 0\n"
109 #endif
110         "    .short LExit_fake_msgSend - _fake_msgSend\n"
111         "    .short LRecover_fake_msgSend - _fake_msgSend\n"
112         "    .long 0\n");
113 
114 static void
noop_signal(int signo __unused)115 noop_signal(int signo __unused)
116 {
117 }
118 
119 static void *
task_restartable_ranges_thread(void * _ctx)120 task_restartable_ranges_thread(void *_ctx)
121 {
122 	int *stepp = _ctx;
123 	restartable_function(stepp); // increments step
124 	T_PASS("was successfully restarted\n");
125 	(*stepp)++;
126 	return NULL;
127 }
128 
129 static void
wait_for_step(int which)130 wait_for_step(int which)
131 {
132 	for (int i = 0; step != which && i < 10; i++) {
133 		usleep(100000);
134 	}
135 }
136 
137 #endif
138 
139 T_DECL(task_restartable_ranges, "test task_restartable_ranges")
140 {
141 #ifdef SKIP_TEST
142 	T_SKIP("Not supported");
143 #else
144 	kern_return_t kr;
145 	pthread_t th;
146 	int rc;
147 
148 	signal(SIGUSR1, noop_signal);
149 
150 	kr = task_restartable_ranges_register(mach_task_self(), ranges, 2);
151 	T_ASSERT_MACH_SUCCESS(kr, "task_restartable_ranges_register");
152 
153 	{
154 		rc = pthread_create(&th, NULL, &task_restartable_ranges_thread, &step);
155 		T_ASSERT_POSIX_SUCCESS(rc, "pthread_create");
156 
157 		wait_for_step(1);
158 		T_ASSERT_EQ(step, 1, "The thread started (sync)");
159 
160 		kr = task_restartable_ranges_synchronize(mach_task_self());
161 		T_ASSERT_MACH_SUCCESS(kr, "task_restartable_ranges_synchronize");
162 
163 		T_LOG("wait for the function to be restarted (sync)");
164 		wait_for_step(2);
165 		T_ASSERT_EQ(step, 2, "The thread exited (sync)");
166 		pthread_join(th, NULL);
167 	}
168 
169 	{
170 		rc = pthread_create(&th, NULL, &task_restartable_ranges_thread, &step);
171 		T_ASSERT_POSIX_SUCCESS(rc, "pthread_create");
172 
173 		wait_for_step(3);
174 		T_ASSERT_EQ(step, 3, "The thread started (signal)");
175 
176 		rc = pthread_kill(th, SIGUSR1);
177 		T_ASSERT_POSIX_SUCCESS(rc, "pthread_kill");
178 
179 		T_LOG("wait for the function to be restarted (signal)");
180 		wait_for_step(4);
181 		T_ASSERT_EQ(step, 4, "The thread exited (signal)");
182 		pthread_join(th, NULL);
183 	}
184 #endif
185 }
186 
187 #ifndef SKIP_TEST
188 
189 #define N_BUCKETS 4
190 struct bucket {
191 	char buf[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE)));
192 };
193 
194 static struct bucket arena[N_BUCKETS];
195 static size_t arena_cur = 1;
196 
197 static void *cls[5] = { 0, 0, &arena[0], 0, 0 }; /* our fake objc Class */
198 static void *obj[4] = { cls, 0, 0, 0, };         /* our fake objc object */
199 
200 static volatile long syncs = 1;
201 
202 static void *
arena_alloc(void)203 arena_alloc(void)
204 {
205 	struct bucket *p = &arena[arena_cur++ % N_BUCKETS];
206 
207 	T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect(p, PAGE_MAX_SIZE,
208 	    PROT_READ | PROT_WRITE), "arena_alloc");
209 
210 	return p;
211 }
212 
213 static void
arena_free(void * p)214 arena_free(void *p)
215 {
216 	T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect(p, PAGE_MAX_SIZE,
217 	    PROT_NONE), "arena_free");
218 }
219 
220 static void
task_restartable_ranges_race_fail(int signo)221 task_restartable_ranges_race_fail(int signo)
222 {
223 	T_FAIL("test crashed with signal %s after %d syncs",
224 	    strsignal(signo), syncs);
225 	T_END;
226 }
227 
228 #endif
229 
230 T_DECL(task_restartable_ranges_race, "test for 88873668")
231 {
232 #ifdef SKIP_TEST
233 	T_SKIP("Not supported");
234 #else
235 	kern_return_t kr;
236 	pthread_t th;
237 	void *old;
238 	int rc;
239 
240 	signal(SIGBUS, task_restartable_ranges_race_fail);
241 
242 	kr = task_restartable_ranges_register(mach_task_self(), ranges, 2);
243 	T_ASSERT_MACH_SUCCESS(kr, "task_restartable_ranges_register");
244 
245 	dispatch_async_f(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0),
246 	    obj, fake_msgSend);
247 
248 	T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect(&arena[1],
249 	    (N_BUCKETS - 1) * PAGE_MAX_SIZE, PROT_NONE), "arena_init");
250 
251 	long step  = 16 << 10;
252 	long count = 16;
253 
254 	for (syncs = 1; syncs <= count * step; syncs++) {
255 		/*
256 		 * Simulate obj-c's algorithm:
257 		 *
258 		 * 1. allocate a new bucket
259 		 * 2. publish it
260 		 * 3. synchronize
261 		 * 4. dealloc the old bucket
262 		 */
263 		old = os_atomic_xchg(&cls[2], arena_alloc(), release);
264 
265 		kr = task_restartable_ranges_synchronize(mach_task_self());
266 		if (kr != KERN_SUCCESS) {
267 			T_FAIL("task_restartable_ranges_register failed");
268 			T_END;
269 		}
270 
271 		if (syncs % step == 0) {
272 			T_LOG("%d/%d", syncs / step, count);
273 		}
274 
275 		arena_free(old);
276 	}
277 
278 	T_PASS("survived without crashing");
279 #endif
280 }
281