mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2025-01-24 12:35:55 +08:00
d137e6dc79
Say: <stopped at a breakpoint in thread 2> (gdb) thread 3 (gdb) step The above triggers the prepare_to_proceed/deferred_step_ptid process, which switches back to thread 2, to step over its breakpoint before getting back to thread 3 and "step" it. If while stepping over the breakpoint in thread 2, a signal arrives, and it is set to pass/nostop, we'll set a step-resume breakpoint at the supposed signal-handler resume address, and call keep_going. The problem is that we were supposedly stepping thread 3, and that keep_going delivers a signal to thread 2, and due to scheduler-locking off, resumes everything else, _including_ thread 3, the thread we want stepping. This means that we lose control of thread 3 until the next event, when we stop everything. The end result for the user, is that GDB lost control of the "step". Here's the current infrun debug output of the above, with the testcase in the patch below: infrun: clear_proceed_status_thread (Thread 0x2aaaab8f5700 (LWP 11663)) infrun: clear_proceed_status_thread (Thread 0x2aaaab6f4700 (LWP 11662)) infrun: clear_proceed_status_thread (Thread 0x2aaaab4f2b20 (LWP 11659)) infrun: proceed (addr=0xffffffffffffffff, signal=144, step=1) infrun: prepare_to_proceed (step=1), switched to [Thread 0x2aaaab6f4700 (LWP 11662)] infrun: resume (step=1, signal=0), trap_expected=1, current thread [Thread 0x2aaaab6f4700 (LWP 11662)] at 0x40098f infrun: wait_for_inferior () infrun: target_wait (-1, status) = infrun: 11659 [Thread 0x2aaaab6f4700 (LWP 11662)], infrun: status->kind = stopped, signal = SIGUSR1 infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x40098f infrun: random signal 30 Program received signal SIGUSR1, User defined signal 1. infrun: signal arrived while stepping over breakpoint infrun: inserting step-resume breakpoint at 0x40098f infrun: resume (step=0, signal=30), trap_expected=0, current thread [Thread 0x2aaaab6f4700 (LWP 11662)] at 0x40098f ^^^ this is a wildcard resume. infrun: prepare_to_wait infrun: target_wait (-1, status) = infrun: 11659 [Thread 0x2aaaab6f4700 (LWP 11662)], infrun: status->kind = stopped, signal = SIGTRAP infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x40098f infrun: BPSTAT_WHAT_STEP_RESUME infrun: resume (step=1, signal=0), trap_expected=1, current thread [Thread 0x2aaaab6f4700 (LWP 11662)] at 0x40098f ^^^ step-resume hit, meaning the handler returned, so we go back to stepping thread 3. infrun: prepare_to_wait infrun: target_wait (-1, status) = infrun: 11659 [Thread 0x2aaaab6f4700 (LWP 11662)], infrun: status->kind = stopped, signal = SIGTRAP infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x40088b infrun: switching back to stepped thread infrun: Switching context from Thread 0x2aaaab6f4700 (LWP 11662) to Thread 0x2aaaab8f5700 (LWP 11663) infrun: resume (step=1, signal=0), trap_expected=0, current thread [Thread 0x2aaaab8f5700 (LWP 11663)] at 0x400938 infrun: prepare_to_wait infrun: target_wait (-1, status) = infrun: 11659 [Thread 0x2aaaab8f5700 (LWP 11663)], infrun: status->kind = stopped, signal = SIGTRAP infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x40093a infrun: keep going infrun: resume (step=1, signal=0), trap_expected=0, current thread [Thread 0x2aaaab8f5700 (LWP 11663)] at 0x40093a infrun: prepare_to_wait infrun: target_wait (-1, status) = infrun: 11659 [Thread 0x2aaaab8f5700 (LWP 11663)], infrun: status->kind = stopped, signal = SIGTRAP infrun: infwait_normal_state infrun: TARGET_WAITKIND_STOPPED infrun: stop_pc = 0x40091e infrun: stepped to a different line infrun: stop_stepping [Switching to Thread 0x2aaaab8f5700 (LWP 11663)] 69 (*myp) ++; /* set breakpoint child_two here */ ^^^ we stopped at the wrong line. We still stepped a bit because the test is running in a loop, and when we got back to stepping thread 3, it happened to be in the stepping range. (The loop increments a counter, and the test makes sure it increments exactly once. Without the fix, the counter increments a bunch, since the user-stepped thread runs free without GDB noticing.) The fix is to switch to the stepping thread before continuing for the step-resume breakpoint. gdb/ 2014-02-07 Pedro Alves <palves@redhat.com> * infrun.c (handle_signal_stop) <signal arrives while stepping over a breakpoint>: Switch back to the stepping thread. gdb/testsuite/ 2014-02-07 Pedro Alves <pedro@codesourcery.com> Pedro Alves <palves@redhat.com> * gdb.threads/step-after-sr-lock.c: New file. * gdb.threads/step-after-sr-lock.exp: New file.
146 lines
3.0 KiB
C
146 lines
3.0 KiB
C
/* This testcase is part of GDB, the GNU debugger.
|
|
|
|
Copyright 2009-2014 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 <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
|
|
unsigned int args[2];
|
|
|
|
pid_t pid;
|
|
pthread_barrier_t barrier;
|
|
pthread_t child_thread_2, child_thread_3;
|
|
|
|
void
|
|
handler (int signo)
|
|
{
|
|
/* so that thread 3 is sure to run, in case the bug is present. */
|
|
usleep (10);
|
|
}
|
|
|
|
void
|
|
callme (void)
|
|
{
|
|
}
|
|
|
|
void
|
|
block_signals (void)
|
|
{
|
|
sigset_t mask;
|
|
|
|
sigfillset (&mask);
|
|
sigprocmask (SIG_BLOCK, &mask, NULL);
|
|
}
|
|
|
|
void
|
|
unblock_signals (void)
|
|
{
|
|
sigset_t mask;
|
|
|
|
sigfillset (&mask);
|
|
sigprocmask (SIG_UNBLOCK, &mask, NULL);
|
|
}
|
|
|
|
void *
|
|
child_function_3 (void *arg)
|
|
{
|
|
int my_number = (long) arg;
|
|
volatile int *myp = (int *) &args[my_number];
|
|
|
|
pthread_barrier_wait (&barrier);
|
|
|
|
while (*myp > 0)
|
|
{
|
|
(*myp) ++; /* set breakpoint child_two here */
|
|
callme ();
|
|
}
|
|
|
|
pthread_exit (NULL);
|
|
}
|
|
|
|
void *
|
|
child_function_2 (void *arg)
|
|
{
|
|
int my_number = (long) arg;
|
|
volatile int *myp = (int *) &args[my_number];
|
|
|
|
unblock_signals ();
|
|
|
|
pthread_barrier_wait (&barrier);
|
|
|
|
while (*myp > 0)
|
|
{
|
|
(*myp) ++;
|
|
callme (); /* set breakpoint child_one here */
|
|
}
|
|
|
|
*myp = 1;
|
|
while (*myp > 0)
|
|
{
|
|
(*myp) ++;
|
|
callme ();
|
|
}
|
|
|
|
pthread_exit (NULL);
|
|
}
|
|
|
|
|
|
int
|
|
main ()
|
|
{
|
|
int res;
|
|
long i;
|
|
|
|
/* Block signals in all threads but one, so that we're sure which
|
|
thread gets the signal we send from the command line. */
|
|
block_signals ();
|
|
|
|
signal (SIGUSR1, handler);
|
|
|
|
/* Call these early so that PLTs for these are resolved soon,
|
|
instead of in the threads. RTLD_NOW should work as well. */
|
|
usleep (0);
|
|
pthread_barrier_init (&barrier, NULL, 1);
|
|
pthread_barrier_wait (&barrier);
|
|
|
|
pthread_barrier_init (&barrier, NULL, 2);
|
|
|
|
/* The test uses this global to know where to send the signal
|
|
to. */
|
|
pid = getpid ();
|
|
|
|
i = 0;
|
|
args[i] = 1;
|
|
res = pthread_create (&child_thread_2,
|
|
NULL, child_function_2, (void *) i);
|
|
pthread_barrier_wait (&barrier);
|
|
callme (); /* set wait-thread-2 breakpoint here */
|
|
|
|
i = 1;
|
|
args[i] = 1;
|
|
res = pthread_create (&child_thread_3,
|
|
NULL, child_function_3, (void *) i);
|
|
pthread_barrier_wait (&barrier);
|
|
callme (); /* set wait-thread-3 breakpoint here */
|
|
|
|
pthread_join (child_thread_2, NULL);
|
|
pthread_join (child_thread_3, NULL);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|