| /* 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; |
| } |