mirror of
https://github.com/espressif/binutils-gdb.git
synced 2025-12-19 01:19:41 +08:00
I noticed that if GDB is using a remote or extended-remote target, then, if an inferior call caused a new thread to appear, or for an existing thread to exit, then these events are not reported to the user. The problem is that for these targets GDB relies on a call to update_thread_list to learn about changes to the inferior's thread list. If GDB doesn't pass through the normal stop code then GDB will not call update_thread_list, and so will not report changes in the thread list. This commit adds an additional update_thread_list call, after which thread events are correctly reported.
193 lines
5.2 KiB
C
193 lines
5.2 KiB
C
/* 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 <assert.h>
|
|
#include <pthread.h>
|
|
|
|
/* Test can create at most this number of extra threads. */
|
|
#define MAX_THREADS 3
|
|
|
|
/* For convenience. */
|
|
#define FALSE 0
|
|
#define TRUE (!FALSE)
|
|
|
|
/* Controls a thread created by this test. */
|
|
struct thread_descriptor
|
|
{
|
|
/* The pthread handle. Not valid unless STARTED is true. */
|
|
pthread_t thr;
|
|
|
|
/* This field is set to TRUE when a thread has been created, otherwise,
|
|
is false. */
|
|
int started;
|
|
|
|
/* A condition variable and mutex, used for synchronising between the
|
|
worker thread and the main thread. */
|
|
pthread_cond_t cond;
|
|
pthread_mutex_t mutex;
|
|
};
|
|
|
|
/* Keep track of worker threads. */
|
|
struct thread_descriptor threads[MAX_THREADS];
|
|
|
|
/* Worker thread function. Doesn't do much. Synchronise with the main
|
|
thread, mark the thread as started, and then block waiting for the main
|
|
thread. Once the main thread wakes us, this thread exits.
|
|
|
|
ARG is a thread_descriptor shared with the main thread. */
|
|
|
|
void *
|
|
thread_function (void *arg)
|
|
{
|
|
int res;
|
|
struct thread_descriptor *thread = (struct thread_descriptor *) arg;
|
|
|
|
/* Acquire the thread's lock. Initially the main thread holds this lock,
|
|
but releases it when the main thread enters a pthread_cond_wait. */
|
|
res = pthread_mutex_lock (&thread->mutex);
|
|
assert (res == 0);
|
|
|
|
/* Mark the thread as started. */
|
|
thread->started = TRUE;
|
|
|
|
/* Signal the main thread to tell it we are started. The main thread
|
|
will still be blocked though as we hold the thread's lock. */
|
|
res = pthread_cond_signal (&thread->cond);
|
|
assert (res == 0);
|
|
|
|
/* Now wait until the main thread tells us to exit. By entering this
|
|
pthread_cond_wait we release the lock, which allows the main thread to
|
|
resume. */
|
|
res = pthread_cond_wait (&thread->cond, &thread->mutex);
|
|
assert (res == 0);
|
|
|
|
/* The main thread woke us up. We reacquired the thread lock as we left
|
|
the pthread_cond_wait, so release the lock now. */
|
|
res = pthread_mutex_unlock (&thread->mutex);
|
|
assert (res == 0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Start a new thread within the global THREADS array. Return true if a
|
|
new thread was started, otherwise return false. */
|
|
|
|
int
|
|
start_thread ()
|
|
{
|
|
int idx, res;
|
|
|
|
for (idx = 0; idx < MAX_THREADS; ++idx)
|
|
if (!threads[idx].started)
|
|
break;
|
|
|
|
if (idx == MAX_THREADS)
|
|
return FALSE;
|
|
|
|
/* Acquire the thread lock before starting the new thread. */
|
|
res = pthread_mutex_lock (&threads[idx].mutex);
|
|
assert (res == 0);
|
|
|
|
/* Start the new thread. */
|
|
res = pthread_create (&threads[idx].thr, NULL,
|
|
thread_function, &threads[idx]);
|
|
assert (res == 0);
|
|
|
|
/* Unlock and wait. The thread signals us once it is ready. */
|
|
res = pthread_cond_wait (&threads[idx].cond, &threads[idx].mutex);
|
|
assert (res == 0);
|
|
|
|
/* The worker thread is now blocked in a pthread_cond_wait and we
|
|
reacquired the lock as we left our own pthread_cond_wait above. */
|
|
res = pthread_mutex_unlock (&threads[idx].mutex);
|
|
assert (res == 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Stop a thread from within the global THREADS array. Return true if a
|
|
thread was stopped, otherwise return false. */
|
|
int
|
|
stop_thread ()
|
|
{
|
|
/* Look for a thread that is started. */
|
|
for (int idx = 0; idx < MAX_THREADS; ++idx)
|
|
if (threads[idx].started)
|
|
{
|
|
int res;
|
|
|
|
/* Grab the thread lock. */
|
|
res = pthread_mutex_lock (&threads[idx].mutex);
|
|
assert (res == 0);
|
|
|
|
/* Signal the worker thread, this wakes it up, but it can't exit
|
|
until it acquires the thread lock, which we currently hold. */
|
|
res = pthread_cond_signal (&threads[idx].cond);
|
|
assert (res == 0);
|
|
|
|
/* Release the thread lock, this allows the worker thread to exit. */
|
|
res = pthread_mutex_unlock (&threads[idx].mutex);
|
|
assert (res == 0);
|
|
|
|
/* Now wait for the thread to exit. */
|
|
void *retval;
|
|
res = pthread_join (threads[idx].thr, &retval);
|
|
assert (res == 0);
|
|
assert (retval == NULL);
|
|
|
|
/* Now the thread has exited, mark it as no longer started. */
|
|
assert (threads[idx].started);
|
|
threads[idx].started = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
init_descriptor_array ()
|
|
{
|
|
for (int i = 0; i < MAX_THREADS; ++i)
|
|
{
|
|
int res;
|
|
|
|
threads[i].started = FALSE;
|
|
res = pthread_cond_init (&threads[i].cond, NULL);
|
|
assert (res == 0);
|
|
res = pthread_mutex_init (&threads[i].mutex, NULL);
|
|
assert (res == 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
breakpt ()
|
|
{
|
|
/* Nothing. */
|
|
}
|
|
|
|
int
|
|
main ()
|
|
{
|
|
init_descriptor_array ();
|
|
breakpt ();
|
|
start_thread ();
|
|
stop_thread ();
|
|
breakpt ();
|
|
return 0;
|
|
}
|