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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
|
/* This testcase is part of GDB, the GNU debugger.
Copyright 2023 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <pthread.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
/* The delay that the main thread gives once all the worker threads have
reached the barrier before the main thread enters the function on which
GDB will have placed a breakpoint. */
#define MAIN_THREAD_DELAY 2
/* The maximum time we allow this test program to run for before an alarm
signal is sent and everything will exit. */
#define WATCHDOG_ALARM_TIME 600
/* Aliases for the signals used within this script. Each signal
corresponds to an action (from the FINAL_ACTION enum) that the signal
handler will perform. */
#define SPIN_SIGNAL SIGUSR1
#define SYSCALL_SIGNAL SIGUSR2
/* Describe the final action that a thread should perform. */
enum final_action
{
/* Thread should spin in an infinite loop. */
SPIN = 0,
/* Thread should block in a syscall. */
SYSCALL,
/* This is just a marker to allow for looping over the enum. */
LAST_ACTION
};
/* Where should the thread perform this action? */
enum exec_location
{
/* Just a normal thread, on a normal stack. */
NORMAL = 0,
/* In a signal handler, but use the normal stack. */
SIGNAL_HANDLER,
/* In a signal handler using an alternative stack. */
SIGNAL_ALT_STACK,
/* This is just a marker to allow for looping over the enum. */
LAST_LOCACTION
};
/* A descriptor for a single thread job. We create a new thread for each
job_description. */
struct job_description
{
/* What action should this thread perform. */
enum final_action action;
/* Where should the thread perform the action. */
enum exec_location location;
/* The actual thread handle, so we can join with the thread. */
pthread_t thread;
};
/* A pthread barrier, used to (try) and synchronise the threads. */
pthread_barrier_t global_barrier;
/* Return a list of jobs, and place the length of the list in *COUNT. */
struct job_description *
get_job_list (int *count)
{
/* The number of jobs. */
int num = LAST_ACTION * LAST_LOCACTION;
/* The uninitialised array of jobs. */
struct job_description *list
= malloc (num * sizeof (struct job_description));
assert (list != NULL);
/* Fill the array with all possible jobs. */
for (int i = 0; i < (int) LAST_ACTION; ++i)
for (int j = 0; j < (int) LAST_LOCACTION; ++j)
{
int idx = (i * LAST_LOCACTION) + j;
list[idx].action = (enum final_action) i;
list[idx].location = (enum exec_location) j;
}
/* Return the array of jobs. */
*count = num;
return list;
}
/* This function should never be called. If it is then an assertion will
trigger. */
void
assert_not_reached (void)
{
assert (0);
}
/* The function for a SPIN action. Just spins in a loop. The LOCATION
argument exists so GDB can identify the expected context for this
function. */
void
do_spin_task (enum exec_location location)
{
(void) location;
/* Let everyone know that we're about to perform our action. */
int res = pthread_barrier_wait (&global_barrier);
assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
while (1)
{
/* Nothing. */
}
}
/* The function for a SYSCALL action. Just spins in a loop. The LOCATION
argument exists so GDB can identify the expected context for this
function. */
void
do_syscall_task (enum exec_location location)
{
(void) location;
/* Let everyone know that we're about to perform our action. */
int res = pthread_barrier_wait (&global_barrier);
assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
sleep (600);
}
/* Return the required size for a sigaltstack. We start with a single
page, but do check against the system defined minimums. We don't run
much on the alternative stacks, so we don't need a huge one. */
size_t
get_stack_size (void)
{
size_t size = getpagesize (); /* Arbitrary starting size. */
if (size < SIGSTKSZ)
size = SIGSTKSZ;
if (size < MINSIGSTKSZ)
size = MINSIGSTKSZ;
return size;
}
/* A descriptor for an alternative stack. */
struct stack_descriptor
{
/* The base address of the alternative stack. This is the address that
must be freed to release the memory used by this stack. */
void *base;
/* The size of this alternative stack. Tracked just so we can query this
from GDB. */
size_t size;
};
/* Install an alternative signal stack. Return a descriptor for the newly
allocated alternative stack. */
struct stack_descriptor
setup_alt_stack (void)
{
size_t stack_size = get_stack_size ();
void *stack_area = malloc (stack_size);
stack_t stk;
stk.ss_sp = stack_area;
stk.ss_flags = 0;
stk.ss_size = stack_size;
int res = sigaltstack (&stk, NULL);
assert (res == 0);
struct stack_descriptor desc;
desc.base = stack_area;
desc.size = stack_size;
return desc;
}
/* Return true (non-zero) if we are currently on the alternative stack,
otherwise, return false (zero). */
int
on_alt_stack_p (void)
{
stack_t stk;
int res = sigaltstack (NULL, &stk);
assert (res == 0);
return (stk.ss_flags & SS_ONSTACK) != 0;
}
/* The signal handler function. All signals call here, so we use SIGNO
(the signal that was delivered) to decide what action to perform. This
function might, or might not, have been called on an alternative signal
stack. */
void
signal_handler (int signo)
{
enum exec_location location
= on_alt_stack_p () ? SIGNAL_ALT_STACK : SIGNAL_HANDLER;
switch (signo)
{
case SPIN_SIGNAL:
do_spin_task (location);
break;
case SYSCALL_SIGNAL:
do_syscall_task (location);
break;
default:
assert_not_reached ();
}
}
/* The thread worker function. ARG is a job_description pointer which
describes what this thread is expected to do. This function always
returns a NULL pointer. */
void *
thread_function (void *arg)
{
struct job_description *job = (struct job_description *) arg;
struct stack_descriptor desc = { NULL, 0 };
int sa_flags = 0;
switch (job->location)
{
case NORMAL:
/* This thread performs the worker action on the current thread,
select the correct worker function based on the requested
action. */
switch (job->action)
{
case SPIN:
do_spin_task (NORMAL);
break;
case SYSCALL:
do_syscall_task (NORMAL);
break;
default:
assert_not_reached ();
}
break;
case SIGNAL_ALT_STACK:
/* This thread is to perform its action in a signal handler on the
alternative stack. Install the alternative stack now, and then
fall through to the normal signal handler location code. */
desc = setup_alt_stack ();
assert (desc.base != NULL);
assert (desc.size > 0);
sa_flags = SA_ONSTACK;
/* Fall through. */
case SIGNAL_HANDLER:
{
/* This thread is to perform its action in a signal handler. We
might have just installed an alternative signal stack. */
int signo, res;
/* Select the correct signal number so that the signal handler will
perform the required action. */
switch (job->action)
{
case SPIN:
signo = SPIN_SIGNAL;
break;
case SYSCALL:
signo = SYSCALL_SIGNAL;
break;
default:
assert_not_reached ();
}
/* Now setup the signal handler. */
struct sigaction sa;
sa.sa_handler = signal_handler;
sigfillset (&sa.sa_mask);
sa.sa_flags = sa_flags;
res = sigaction (signo, &sa, NULL);
assert (res == 0);
/* Send the signal to this thread. */
res = pthread_kill (job->thread, signo);
assert (res == 0);
}
break;
default:
assert_not_reached ();
};
/* Free the alt-stack if we allocated one, if not DESC.BASE will be
NULL so this call is fine. */
free (desc.base);
/* Thread complete. */
return NULL;
}
void
start_job (struct job_description *job)
{
int res;
res = pthread_create (&job->thread, NULL, thread_function, job);
assert (res == 0);
}
/* Join with the thread for JOB. This will block until the thread for JOB
has finished. */
void
finalise_job (struct job_description *job)
{
int res;
void *retval;
res = pthread_join (job->thread, &retval);
assert (res == 0);
assert (retval == NULL);
}
/* Function that GDB can place a breakpoint on. */
void
breakpt (void)
{
/* Nothing. */
}
/* Function that triggers a crash, if the user has setup their environment
correctly this will dump a core file, which GDB can then examine. */
void
crash_function (void)
{
volatile int *p = 0;
volatile int n = *p;
(void) n;
}
/* Entry point. */
int
main ()
{
int job_count, res;
struct job_description *jobs = get_job_list (&job_count);
/* This test is going to park some threads inside infinite loops. Just
in case this program is left running, install an alarm that will cause
everything to exit. */
alarm (WATCHDOG_ALARM_TIME);
/* We want each worker thread (of which there are JOB_COUNT) plus the
main thread (hence + 1) to wait at the barrier. */
res = pthread_barrier_init (&global_barrier, NULL, job_count + 1);
assert (res == 0);
/* Start all the jobs. */
for (int i = 0; i < job_count; ++i)
start_job (&jobs[i]);
/* Notify all the worker threads that we're waiting for them. */
res = pthread_barrier_wait (&global_barrier);
assert (res == PTHREAD_BARRIER_SERIAL_THREAD || res == 0);
/* All we know at this point is that all the worker threads have reached
the barrier, which is just before they perform their action. But we
really want them to start their action.
There's really no way we can be 100% certain that the worker threads
have started their action, all we can do is wait for a short while and
hope that the machine we're running on is not too slow. */
sleep (MAIN_THREAD_DELAY);
/* A function that GDB can place a breakpoint on. By the time we get
here we are as sure as we can be that all of the worker threads have
started and are in their worker action (spinning, or syscall). */
breakpt ();
/* If GDB is not attached then this function will cause a crash, which
can be used to dump a core file, which GDB can then analyse. */
crash_function ();
/* Due to the crash we never expect to get here. Plus the worker actions
never terminate. But for completeness, here's where we join with all
the worker threads. */
for (int i = 0; i < job_count; ++i)
finalise_job (&jobs[i]);
/* Cleanup the barrier. */
res = pthread_barrier_destroy (&global_barrier);
assert (res == 0);
/* And clean up the jobs list. */
free (jobs);
return 0;
}
|