aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog20
-rw-r--r--gdb/gdbthread.h7
-rw-r--r--gdb/infrun.c372
-rw-r--r--gdb/linux-nat.c208
-rw-r--r--gdb/remote.c26
-rw-r--r--gdb/target.h14
-rw-r--r--gdb/testsuite/ChangeLog8
-rw-r--r--gdb/testsuite/gdb.threads/siginfo-threads.c457
-rw-r--r--gdb/testsuite/gdb.threads/siginfo-threads.exp99
-rw-r--r--gdb/testsuite/gdb.threads/sigstep-threads.c54
-rw-r--r--gdb/testsuite/gdb.threads/sigstep-threads.exp73
-rw-r--r--gdb/thread.c2
12 files changed, 996 insertions, 344 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index d291ae6..1780862 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,23 @@
+2012-06-28 Jan Kratochvil <jan.kratochvil@redhat.com>
+ Pedro Alves <palves@redhat.com>
+
+ * gdbthread.h (ALL_THREADS): New macro.
+ (thread_list): Declare.
+ * infrun.c (handle_inferior_event) <spurious signal>: Don't keep
+ going, but instead fall through to the stepping handling.
+ * linux-nat.c (resume_lwp): New parameter 'signo'. Resume with
+ the passed in signal. Adjust debug output.
+ (resume_callback): Rename to ...
+ (linux_nat_resume_callback): ... this. Pass the thread's last
+ stop signal, if in "pass" state.
+ (linux_nat_resume): Adjust to rename.
+ (stop_wait_callback): New assertion. Don't respawn signals;
+ instead let the LWP remain with SIGNALLED set.
+ (linux_nat_wait_1): Remove flushing of pending SIGSTOPs.
+ * remote.c (append_pending_thread_resumptions): New.
+ (remote_vcont_resume): Call it.
+ * target.h (target_resume): Extend comment.
+
2012-06-28 Iain Sandoe <iain@codesourcery.com>
* auxv.c (fprint_target_auxv): Handle extended cache data tags.
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 7cd66b6..0250555 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -301,6 +301,11 @@ void thread_change_ptid (ptid_t old_ptid, ptid_t new_ptid);
typedef int (*thread_callback_func) (struct thread_info *, void *);
extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
+/* Traverse all threads. */
+
+#define ALL_THREADS(T) \
+ for (T = thread_list; T; T = T->next)
+
extern int thread_count (void);
/* Switch from one thread to another. */
@@ -391,4 +396,6 @@ extern struct thread_info* inferior_thread (void);
extern void update_thread_list (void);
+extern struct thread_info *thread_list;
+
#endif /* GDBTHREAD_H */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 53db335..2f66ddc 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4363,227 +4363,227 @@ process_event_stop_test:
(leaving the inferior at the step-resume-breakpoint without
actually executing it). Either way continue until the
breakpoint is really hit. */
- keep_going (ecs);
- return;
}
+ else
+ {
+ /* Handle cases caused by hitting a breakpoint. */
- /* Handle cases caused by hitting a breakpoint. */
- {
- CORE_ADDR jmp_buf_pc;
- struct bpstat_what what;
-
- what = bpstat_what (ecs->event_thread->control.stop_bpstat);
-
- if (what.call_dummy)
- {
- stop_stack_dummy = what.call_dummy;
- }
-
- /* If we hit an internal event that triggers symbol changes, the
- current frame will be invalidated within bpstat_what (e.g., if
- we hit an internal solib event). Re-fetch it. */
- frame = get_current_frame ();
- gdbarch = get_frame_arch (frame);
-
- switch (what.main_action)
- {
- case BPSTAT_WHAT_SET_LONGJMP_RESUME:
- /* If we hit the breakpoint at longjmp while stepping, we
- install a momentary breakpoint at the target of the
- jmp_buf. */
-
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME\n");
+ CORE_ADDR jmp_buf_pc;
+ struct bpstat_what what;
- ecs->event_thread->stepping_over_breakpoint = 1;
+ what = bpstat_what (ecs->event_thread->control.stop_bpstat);
- if (what.is_longjmp)
- {
- struct value *arg_value;
-
- /* If we set the longjmp breakpoint via a SystemTap probe,
- then use it to extract the arguments. The destination
- PC is the third argument to the probe. */
- arg_value = probe_safe_evaluate_at_pc (frame, 2);
- if (arg_value)
- jmp_buf_pc = value_as_address (arg_value);
- else if (!gdbarch_get_longjmp_target_p (gdbarch)
- || !gdbarch_get_longjmp_target (gdbarch,
- frame, &jmp_buf_pc))
- {
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME "
- "(!gdbarch_get_longjmp_target)\n");
- keep_going (ecs);
- return;
- }
-
- /* Insert a breakpoint at resume address. */
- insert_longjmp_resume_breakpoint (gdbarch, jmp_buf_pc);
- }
- else
- check_exception_resume (ecs, frame);
- keep_going (ecs);
- return;
-
- case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME:
+ if (what.call_dummy)
{
- struct frame_info *init_frame;
-
- /* There are several cases to consider.
-
- 1. The initiating frame no longer exists. In this case
- we must stop, because the exception or longjmp has gone
- too far.
-
- 2. The initiating frame exists, and is the same as the
- current frame. We stop, because the exception or longjmp
- has been caught.
+ stop_stack_dummy = what.call_dummy;
+ }
- 3. The initiating frame exists and is different from the
- current frame. This means the exception or longjmp has
- been caught beneath the initiating frame, so keep
- going.
+ /* If we hit an internal event that triggers symbol changes, the
+ current frame will be invalidated within bpstat_what (e.g.,
+ if we hit an internal solib event). Re-fetch it. */
+ frame = get_current_frame ();
+ gdbarch = get_frame_arch (frame);
- 4. longjmp breakpoint has been placed just to protect
- against stale dummy frames and user is not interested in
- stopping around longjmps. */
+ switch (what.main_action)
+ {
+ case BPSTAT_WHAT_SET_LONGJMP_RESUME:
+ /* If we hit the breakpoint at longjmp while stepping, we
+ install a momentary breakpoint at the target of the
+ jmp_buf. */
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog,
- "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
+ "infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME\n");
- gdb_assert (ecs->event_thread->control.exception_resume_breakpoint
- != NULL);
- delete_exception_resume_breakpoint (ecs->event_thread);
+ ecs->event_thread->stepping_over_breakpoint = 1;
if (what.is_longjmp)
{
- check_longjmp_breakpoint_for_call_dummy (ecs->event_thread->num);
-
- if (!frame_id_p (ecs->event_thread->initiating_frame))
+ struct value *arg_value;
+
+ /* If we set the longjmp breakpoint via a SystemTap
+ probe, then use it to extract the arguments. The
+ destination PC is the third argument to the
+ probe. */
+ arg_value = probe_safe_evaluate_at_pc (frame, 2);
+ if (arg_value)
+ jmp_buf_pc = value_as_address (arg_value);
+ else if (!gdbarch_get_longjmp_target_p (gdbarch)
+ || !gdbarch_get_longjmp_target (gdbarch,
+ frame, &jmp_buf_pc))
{
- /* Case 4. */
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME "
+ "(!gdbarch_get_longjmp_target)\n");
keep_going (ecs);
return;
}
+
+ /* Insert a breakpoint at resume address. */
+ insert_longjmp_resume_breakpoint (gdbarch, jmp_buf_pc);
}
+ else
+ check_exception_resume (ecs, frame);
+ keep_going (ecs);
+ return;
- init_frame = frame_find_by_id (ecs->event_thread->initiating_frame);
+ case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME:
+ {
+ struct frame_info *init_frame;
- if (init_frame)
- {
- struct frame_id current_id
- = get_frame_id (get_current_frame ());
- if (frame_id_eq (current_id,
- ecs->event_thread->initiating_frame))
- {
- /* Case 2. Fall through. */
- }
- else
- {
- /* Case 3. */
- keep_going (ecs);
- return;
- }
- }
+ /* There are several cases to consider.
- /* For Cases 1 and 2, remove the step-resume breakpoint,
- if it exists. */
- delete_step_resume_breakpoint (ecs->event_thread);
+ 1. The initiating frame no longer exists. In this case
+ we must stop, because the exception or longjmp has gone
+ too far.
- ecs->event_thread->control.stop_step = 1;
- print_end_stepping_range_reason ();
- stop_stepping (ecs);
- }
- return;
-
- case BPSTAT_WHAT_SINGLE:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_SINGLE\n");
- ecs->event_thread->stepping_over_breakpoint = 1;
- /* Still need to check other stuff, at least the case
- where we are stepping and step out of the right range. */
- break;
+ 2. The initiating frame exists, and is the same as the
+ current frame. We stop, because the exception or
+ longjmp has been caught.
- case BPSTAT_WHAT_STEP_RESUME:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STEP_RESUME\n");
+ 3. The initiating frame exists and is different from
+ the current frame. This means the exception or longjmp
+ has been caught beneath the initiating frame, so keep
+ going.
- delete_step_resume_breakpoint (ecs->event_thread);
- if (ecs->event_thread->control.proceed_to_finish
- && execution_direction == EXEC_REVERSE)
- {
- struct thread_info *tp = ecs->event_thread;
-
- /* We are finishing a function in reverse, and just hit
- the step-resume breakpoint at the start address of the
- function, and we're almost there -- just need to back
- up by one more single-step, which should take us back
- to the function call. */
- tp->control.step_range_start = tp->control.step_range_end = 1;
- keep_going (ecs);
- return;
- }
- fill_in_stop_func (gdbarch, ecs);
- if (stop_pc == ecs->stop_func_start
- && execution_direction == EXEC_REVERSE)
- {
- /* We are stepping over a function call in reverse, and
- just hit the step-resume breakpoint at the start
- address of the function. Go back to single-stepping,
- which should take us back to the function call. */
- ecs->event_thread->stepping_over_breakpoint = 1;
- keep_going (ecs);
- return;
- }
- break;
+ 4. longjmp breakpoint has been placed just to protect
+ against stale dummy frames and user is not interested
+ in stopping around longjmps. */
- case BPSTAT_WHAT_STOP_NOISY:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STOP_NOISY\n");
- stop_print_frame = 1;
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
- /* We are about to nuke the step_resume_breakpointt via the
- cleanup chain, so no need to worry about it here. */
+ gdb_assert (ecs->event_thread->control.exception_resume_breakpoint
+ != NULL);
+ delete_exception_resume_breakpoint (ecs->event_thread);
- stop_stepping (ecs);
- return;
+ if (what.is_longjmp)
+ {
+ check_longjmp_breakpoint_for_call_dummy (ecs->event_thread->num);
- case BPSTAT_WHAT_STOP_SILENT:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STOP_SILENT\n");
- stop_print_frame = 0;
+ if (!frame_id_p (ecs->event_thread->initiating_frame))
+ {
+ /* Case 4. */
+ keep_going (ecs);
+ return;
+ }
+ }
- /* We are about to nuke the step_resume_breakpoin via the
- cleanup chain, so no need to worry about it here. */
+ init_frame = frame_find_by_id (ecs->event_thread->initiating_frame);
- stop_stepping (ecs);
- return;
+ if (init_frame)
+ {
+ struct frame_id current_id
+ = get_frame_id (get_current_frame ());
+ if (frame_id_eq (current_id,
+ ecs->event_thread->initiating_frame))
+ {
+ /* Case 2. Fall through. */
+ }
+ else
+ {
+ /* Case 3. */
+ keep_going (ecs);
+ return;
+ }
+ }
- case BPSTAT_WHAT_HP_STEP_RESUME:
- if (debug_infrun)
- fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_HP_STEP_RESUME\n");
+ /* For Cases 1 and 2, remove the step-resume breakpoint,
+ if it exists. */
+ delete_step_resume_breakpoint (ecs->event_thread);
- delete_step_resume_breakpoint (ecs->event_thread);
- if (ecs->event_thread->step_after_step_resume_breakpoint)
- {
- /* Back when the step-resume breakpoint was inserted, we
- were trying to single-step off a breakpoint. Go back
- to doing that. */
- ecs->event_thread->step_after_step_resume_breakpoint = 0;
- ecs->event_thread->stepping_over_breakpoint = 1;
- keep_going (ecs);
- return;
+ ecs->event_thread->control.stop_step = 1;
+ print_end_stepping_range_reason ();
+ stop_stepping (ecs);
}
- break;
+ return;
- case BPSTAT_WHAT_KEEP_CHECKING:
- break;
- }
- }
+ case BPSTAT_WHAT_SINGLE:
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_SINGLE\n");
+ ecs->event_thread->stepping_over_breakpoint = 1;
+ /* Still need to check other stuff, at least the case where
+ we are stepping and step out of the right range. */
+ break;
+
+ case BPSTAT_WHAT_STEP_RESUME:
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STEP_RESUME\n");
+
+ delete_step_resume_breakpoint (ecs->event_thread);
+ if (ecs->event_thread->control.proceed_to_finish
+ && execution_direction == EXEC_REVERSE)
+ {
+ struct thread_info *tp = ecs->event_thread;
+
+ /* We are finishing a function in reverse, and just hit
+ the step-resume breakpoint at the start address of
+ the function, and we're almost there -- just need to
+ back up by one more single-step, which should take us
+ back to the function call. */
+ tp->control.step_range_start = tp->control.step_range_end = 1;
+ keep_going (ecs);
+ return;
+ }
+ fill_in_stop_func (gdbarch, ecs);
+ if (stop_pc == ecs->stop_func_start
+ && execution_direction == EXEC_REVERSE)
+ {
+ /* We are stepping over a function call in reverse, and
+ just hit the step-resume breakpoint at the start
+ address of the function. Go back to single-stepping,
+ which should take us back to the function call. */
+ ecs->event_thread->stepping_over_breakpoint = 1;
+ keep_going (ecs);
+ return;
+ }
+ break;
+
+ case BPSTAT_WHAT_STOP_NOISY:
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STOP_NOISY\n");
+ stop_print_frame = 1;
+
+ /* We are about to nuke the step_resume_breakpointt via the
+ cleanup chain, so no need to worry about it here. */
+
+ stop_stepping (ecs);
+ return;
+
+ case BPSTAT_WHAT_STOP_SILENT:
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_STOP_SILENT\n");
+ stop_print_frame = 0;
+
+ /* We are about to nuke the step_resume_breakpoin via the
+ cleanup chain, so no need to worry about it here. */
+
+ stop_stepping (ecs);
+ return;
+
+ case BPSTAT_WHAT_HP_STEP_RESUME:
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog, "infrun: BPSTAT_WHAT_HP_STEP_RESUME\n");
+
+ delete_step_resume_breakpoint (ecs->event_thread);
+ if (ecs->event_thread->step_after_step_resume_breakpoint)
+ {
+ /* Back when the step-resume breakpoint was inserted, we
+ were trying to single-step off a breakpoint. Go back
+ to doing that. */
+ ecs->event_thread->step_after_step_resume_breakpoint = 0;
+ ecs->event_thread->stepping_over_breakpoint = 1;
+ keep_going (ecs);
+ return;
+ }
+ break;
+
+ case BPSTAT_WHAT_KEEP_CHECKING:
+ break;
+ }
+ }
/* We come here if we hit a breakpoint but should not
stop for it. Possibly we also were stepping
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 33b13fa..b82c248 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -1901,7 +1901,7 @@ linux_nat_detach (struct target_ops *ops, char *args, int from_tty)
/* Resume LP. */
static void
-resume_lwp (struct lwp_info *lp, int step)
+resume_lwp (struct lwp_info *lp, int step, enum gdb_signal signo)
{
if (lp->stopped)
{
@@ -1919,14 +1919,18 @@ resume_lwp (struct lwp_info *lp, int step)
{
if (debug_linux_nat)
fprintf_unfiltered (gdb_stdlog,
- "RC: PTRACE_CONT %s, 0, 0 (resuming sibling)\n",
- target_pid_to_str (lp->ptid));
+ "RC: Resuming sibling %s, %s, %s\n",
+ target_pid_to_str (lp->ptid),
+ (signo != GDB_SIGNAL_0
+ ? strsignal (gdb_signal_to_host (signo))
+ : "0"),
+ step ? "step" : "resume");
if (linux_nat_prepare_to_resume != NULL)
linux_nat_prepare_to_resume (lp);
linux_ops->to_resume (linux_ops,
pid_to_ptid (GET_LWP (lp->ptid)),
- step, GDB_SIGNAL_0);
+ step, signo);
lp->stopped = 0;
lp->step = step;
memset (&lp->siginfo, 0, sizeof (lp->siginfo));
@@ -1949,10 +1953,27 @@ resume_lwp (struct lwp_info *lp, int step)
}
}
+/* Resume LWP, with the last stop signal, if it is in pass state. */
+
static int
-resume_callback (struct lwp_info *lp, void *data)
+linux_nat_resume_callback (struct lwp_info *lp, void *data)
{
- resume_lwp (lp, 0);
+ enum gdb_signal signo = GDB_SIGNAL_0;
+
+ if (lp->stopped)
+ {
+ struct thread_info *thread;
+
+ thread = find_thread_ptid (lp->ptid);
+ if (thread != NULL)
+ {
+ if (signal_pass_state (thread->suspend.stop_signal))
+ signo = thread->suspend.stop_signal;
+ thread->suspend.stop_signal = GDB_SIGNAL_0;
+ }
+ }
+
+ resume_lwp (lp, 0, signo);
return 0;
}
@@ -2059,11 +2080,11 @@ linux_nat_resume (struct target_ops *ops,
}
/* Mark LWP as not stopped to prevent it from being continued by
- resume_callback. */
+ linux_nat_resume_callback. */
lp->stopped = 0;
if (resume_many)
- iterate_over_lwps (ptid, resume_callback, NULL);
+ iterate_over_lwps (ptid, linux_nat_resume_callback, NULL);
/* Convert to something the lower layer understands. */
ptid = pid_to_ptid (GET_LWP (lp->ptid));
@@ -2881,110 +2902,39 @@ stop_wait_callback (struct lwp_info *lp, void *data)
if (WSTOPSIG (status) != SIGSTOP)
{
- if (linux_nat_status_is_event (status))
- {
- /* If a LWP other than the LWP that we're reporting an
- event for has hit a GDB breakpoint (as opposed to
- some random trap signal), then just arrange for it to
- hit it again later. We don't keep the SIGTRAP status
- and don't forward the SIGTRAP signal to the LWP. We
- will handle the current event, eventually we will
- resume all LWPs, and this one will get its breakpoint
- trap again.
-
- If we do not do this, then we run the risk that the
- user will delete or disable the breakpoint, but the
- thread will have already tripped on it. */
-
- /* Save the trap's siginfo in case we need it later. */
- save_siginfo (lp);
-
- save_sigtrap (lp);
-
- /* Now resume this LWP and get the SIGSTOP event. */
- errno = 0;
- ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, 0);
- if (debug_linux_nat)
- {
- fprintf_unfiltered (gdb_stdlog,
- "PTRACE_CONT %s, 0, 0 (%s)\n",
- target_pid_to_str (lp->ptid),
- errno ? safe_strerror (errno) : "OK");
-
- fprintf_unfiltered (gdb_stdlog,
- "SWC: Candidate SIGTRAP event in %s\n",
- target_pid_to_str (lp->ptid));
- }
- /* Hold this event/waitstatus while we check to see if
- there are any more (we still want to get that SIGSTOP). */
- stop_wait_callback (lp, NULL);
-
- /* Hold the SIGTRAP for handling by linux_nat_wait. If
- there's another event, throw it back into the
- queue. */
- if (lp->status)
- {
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog,
- "SWC: kill %s, %s\n",
- target_pid_to_str (lp->ptid),
- status_to_str ((int) status));
- kill_lwp (GET_LWP (lp->ptid), WSTOPSIG (lp->status));
- }
+ /* The thread was stopped with a signal other than SIGSTOP. */
- /* Save the sigtrap event. */
- lp->status = status;
- return 0;
- }
- else
- {
- /* The thread was stopped with a signal other than
- SIGSTOP, and didn't accidentally trip a breakpoint. */
+ /* Save the trap's siginfo in case we need it later. */
+ save_siginfo (lp);
- if (debug_linux_nat)
- {
- fprintf_unfiltered (gdb_stdlog,
- "SWC: Pending event %s in %s\n",
- status_to_str ((int) status),
- target_pid_to_str (lp->ptid));
- }
- /* Now resume this LWP and get the SIGSTOP event. */
- errno = 0;
- ptrace (PTRACE_CONT, GET_LWP (lp->ptid), 0, 0);
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog,
- "SWC: PTRACE_CONT %s, 0, 0 (%s)\n",
- target_pid_to_str (lp->ptid),
- errno ? safe_strerror (errno) : "OK");
-
- /* Hold this event/waitstatus while we check to see if
- there are any more (we still want to get that SIGSTOP). */
- stop_wait_callback (lp, NULL);
-
- /* If the lp->status field is still empty, use it to
- hold this event. If not, then this event must be
- returned to the event queue of the LWP. */
- if (lp->status)
- {
- if (debug_linux_nat)
- {
- fprintf_unfiltered (gdb_stdlog,
- "SWC: kill %s, %s\n",
- target_pid_to_str (lp->ptid),
- status_to_str ((int) status));
- }
- kill_lwp (GET_LWP (lp->ptid), WSTOPSIG (status));
- }
- else
- lp->status = status;
- return 0;
- }
+ save_sigtrap (lp);
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "SWC: Pending event %s in %s\n",
+ status_to_str ((int) status),
+ target_pid_to_str (lp->ptid));
+
+ /* Save the sigtrap event. */
+ lp->status = status;
+ gdb_assert (!lp->stopped);
+ gdb_assert (lp->signalled);
+ lp->stopped = 1;
}
else
{
/* We caught the SIGSTOP that we intended to catch, so
there's no SIGSTOP pending. */
+
+ if (debug_linux_nat)
+ fprintf_unfiltered (gdb_stdlog,
+ "SWC: Delayed SIGSTOP caught for %s.\n",
+ target_pid_to_str (lp->ptid));
+
lp->stopped = 1;
+
+ /* Reset SIGNALLED only after the stop_wait_callback call
+ above as it does gdb_assert on SIGNALLED. */
lp->signalled = 0;
}
}
@@ -3238,7 +3188,7 @@ stop_and_resume_callback (struct lwp_info *lp, void *data)
fprintf_unfiltered (gdb_stdlog,
"SARC: re-resuming LWP %ld\n",
GET_LWP (lp->ptid));
- resume_lwp (lp, lp->step);
+ resume_lwp (lp, lp->step, GDB_SIGNAL_0);
}
else
{
@@ -3612,54 +3562,6 @@ retry:
lp = NULL;
}
- if (lp && lp->signalled && lp->last_resume_kind != resume_stop)
- {
- /* A pending SIGSTOP may interfere with the normal stream of
- events. In a typical case where interference is a problem,
- we have a SIGSTOP signal pending for LWP A while
- single-stepping it, encounter an event in LWP B, and take the
- pending SIGSTOP while trying to stop LWP A. After processing
- the event in LWP B, LWP A is continued, and we'll never see
- the SIGTRAP associated with the last time we were
- single-stepping LWP A. */
-
- /* Resume the thread. It should halt immediately returning the
- pending SIGSTOP. */
- registers_changed ();
- if (linux_nat_prepare_to_resume != NULL)
- linux_nat_prepare_to_resume (lp);
- linux_ops->to_resume (linux_ops, pid_to_ptid (GET_LWP (lp->ptid)),
- lp->step, GDB_SIGNAL_0);
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog,
- "LLW: %s %s, 0, 0 (expect SIGSTOP)\n",
- lp->step ? "PTRACE_SINGLESTEP" : "PTRACE_CONT",
- target_pid_to_str (lp->ptid));
- lp->stopped = 0;
- gdb_assert (lp->resumed);
-
- /* Catch the pending SIGSTOP. */
- status = lp->status;
- lp->status = 0;
-
- stop_wait_callback (lp, NULL);
-
- /* If the lp->status field isn't empty, we caught another signal
- while flushing the SIGSTOP. Return it back to the event
- queue of the LWP, as we already have an event to handle. */
- if (lp->status)
- {
- if (debug_linux_nat)
- fprintf_unfiltered (gdb_stdlog,
- "LLW: kill %s, %s\n",
- target_pid_to_str (lp->ptid),
- status_to_str (lp->status));
- kill_lwp (GET_LWP (lp->ptid), WSTOPSIG (lp->status));
- }
-
- lp->status = status;
- }
-
if (!target_can_async_p ())
{
/* Causes SIGINT to be passed on to the attached process. */
diff --git a/gdb/remote.c b/gdb/remote.c
index 38ecd08..01e3f30 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -4644,6 +4644,28 @@ append_resumption (char *p, char *endp,
return p;
}
+/* Append a vCont continue-with-signal action for threads that have a
+ non-zero stop signal. */
+
+static char *
+append_pending_thread_resumptions (char *p, char *endp, ptid_t ptid)
+{
+ struct thread_info *thread;
+
+ ALL_THREADS (thread)
+ if (ptid_match (thread->ptid, ptid)
+ && !ptid_equal (inferior_ptid, thread->ptid)
+ && thread->suspend.stop_signal != GDB_SIGNAL_0
+ && signal_pass_state (thread->suspend.stop_signal))
+ {
+ p = append_resumption (p, endp, thread->ptid,
+ 0, thread->suspend.stop_signal);
+ thread->suspend.stop_signal = GDB_SIGNAL_0;
+ }
+
+ return p;
+}
+
/* Resume the remote inferior by using a "vCont" packet. The thread
to be resumed is PTID; STEP and SIGGNAL indicate whether the
resumed thread should be single-stepped and/or signalled. If PTID
@@ -4696,6 +4718,10 @@ remote_vcont_resume (ptid_t ptid, int step, enum gdb_signal siggnal)
p = append_resumption (p, endp, inferior_ptid, step, siggnal);
}
+ /* Also pass down any pending signaled resumption for other
+ threads not the current. */
+ p = append_pending_thread_resumptions (p, endp, ptid);
+
/* And continue others without a signal. */
append_resumption (p, endp, ptid, /*step=*/ 0, GDB_SIGNAL_0);
}
diff --git a/gdb/target.h b/gdb/target.h
index 233d355..e5bdf4d 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -920,10 +920,16 @@ extern void target_detach (char *, int);
extern void target_disconnect (char *, int);
-/* Resume execution of the target process PTID. STEP says whether to
- single-step or to run free; SIGGNAL is the signal to be given to
- the target, or GDB_SIGNAL_0 for no signal. The caller may not
- pass GDB_SIGNAL_DEFAULT. */
+/* Resume execution of the target process PTID (or a group of
+ threads). STEP says whether to single-step or to run free; SIGGNAL
+ is the signal to be given to the target, or GDB_SIGNAL_0 for no
+ signal. The caller may not pass GDB_SIGNAL_DEFAULT. A specific
+ PTID means `step/resume only this process id'. A wildcard PTID
+ (all threads, or all threads of process) means `step/resume
+ INFERIOR_PTID, and let other threads (for which the wildcard PTID
+ matches) resume with their 'thread->suspend.stop_signal' signal
+ (usually GDB_SIGNAL_0) if it is in "pass" state, or with no signal
+ if in "no pass" state. */
extern void target_resume (ptid_t ptid, int step, enum gdb_signal signal);
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index 36a3534..0e857df 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,11 @@
+2012-06-28 Jan Kratochvil <jan.kratochvil@redhat.com>
+ Pedro Alves <palves@redhat.com>
+
+ * gdb.threads/siginfo-threads.exp: New file.
+ * gdb.threads/siginfo-threads.c: New file.
+ * gdb.threads/sigstep-threads.exp: New file.
+ * gdb.threads/sigstep-threads.c: New file.
+
2012-06-28 Tom Tromey <tromey@redhat.com>
* gdb.go/package.exp: Partially revert earlier patch; use
diff --git a/gdb/testsuite/gdb.threads/siginfo-threads.c b/gdb/testsuite/gdb.threads/siginfo-threads.c
new file mode 100644
index 0000000..5859088
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/siginfo-threads.c
@@ -0,0 +1,457 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2010-2012 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/>. */
+
+#define _GNU_SOURCE
+#include <pthread.h>
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <unistd.h>
+#include <asm/unistd.h>
+
+#define gettid() syscall (__NR_gettid)
+#define tgkill(tgid, tid, sig) syscall (__NR_tgkill, tgid, tid, sig)
+
+/* Terminate always in the main task. It can lock up with SIGSTOPped
+ GDB otherwise. */
+#define TIMEOUT (gettid () == getpid() ? 10 : 15)
+
+static pid_t thread1_tid;
+static pthread_cond_t thread1_tid_cond
+ = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t thread1_tid_mutex
+ = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
+static int thread1_sigusr1_hit;
+static int thread1_sigusr2_hit;
+
+static pid_t thread2_tid;
+static pthread_cond_t thread2_tid_cond
+ = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t thread2_tid_mutex
+ = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
+static int thread2_sigusr1_hit;
+static int thread2_sigusr2_hit;
+
+static pthread_mutex_t terminate_mutex
+ = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
+
+/* Do not use alarm as it would create a ptrace event which would hang
+ us up if we are being traced by GDB, which we stopped
+ ourselves. */
+
+static void
+timed_mutex_lock (pthread_mutex_t *mutex)
+{
+ int i;
+ struct timespec start, now;
+
+ i = clock_gettime (CLOCK_MONOTONIC, &start);
+ assert (i == 0);
+
+ do
+ {
+ i = pthread_mutex_trylock (mutex);
+ if (i == 0)
+ return;
+ assert (i == EBUSY);
+
+ i = clock_gettime (CLOCK_MONOTONIC, &now);
+ assert (i == 0);
+ assert (now.tv_sec >= start.tv_sec);
+ }
+ while (now.tv_sec - start.tv_sec < TIMEOUT);
+
+ fprintf (stderr, "Timed out waiting for internal lock!\n");
+ exit (EXIT_FAILURE);
+}
+
+static void
+handler (int signo, siginfo_t *siginfo, void *exception)
+{
+ int *varp;
+
+ assert (siginfo->si_signo == signo);
+ assert (siginfo->si_code == SI_TKILL);
+ assert (siginfo->si_pid == getpid ());
+
+ if (gettid () == thread1_tid)
+ {
+ if (signo == SIGUSR1)
+ varp = &thread1_sigusr1_hit;
+ else if (signo == SIGUSR2)
+ varp = &thread1_sigusr2_hit;
+ else
+ assert (0);
+ }
+ else if (gettid () == thread2_tid)
+ {
+ if (signo == SIGUSR1)
+ varp = &thread2_sigusr1_hit;
+ else if (signo == SIGUSR2)
+ varp = &thread2_sigusr2_hit;
+ else
+ assert (0);
+ }
+ else
+ assert (0);
+
+ if (*varp)
+ {
+ fprintf (stderr, "Signal %d for TID %lu has been already hit!\n", signo,
+ (unsigned long) gettid ());
+ exit (EXIT_FAILURE);
+ }
+ *varp = 1;
+}
+
+static void *
+thread1_func (void *unused)
+{
+ int i;
+
+ timed_mutex_lock (&thread1_tid_mutex);
+
+ /* THREAD1_TID_MUTEX must be already locked to avoid a race. */
+ thread1_tid = gettid ();
+
+ i = pthread_cond_signal (&thread1_tid_cond);
+ assert (i == 0);
+ i = pthread_mutex_unlock (&thread1_tid_mutex);
+ assert (i == 0);
+
+ /* Be sure the "t (tracing stop)" test can proceed for both
+ threads. */
+ timed_mutex_lock (&terminate_mutex);
+ i = pthread_mutex_unlock (&terminate_mutex);
+ assert (i == 0);
+
+ if (!thread1_sigusr1_hit)
+ {
+ fprintf (stderr, "Thread 1 signal SIGUSR1 not hit!\n");
+ exit (EXIT_FAILURE);
+ }
+ if (!thread1_sigusr2_hit)
+ {
+ fprintf (stderr, "Thread 1 signal SIGUSR2 not hit!\n");
+ exit (EXIT_FAILURE);
+ }
+
+ return NULL;
+}
+
+static void *
+thread2_func (void *unused)
+{
+ int i;
+
+ timed_mutex_lock (&thread2_tid_mutex);
+
+ /* THREAD2_TID_MUTEX must be already locked to avoid a race. */
+ thread2_tid = gettid ();
+
+ i = pthread_cond_signal (&thread2_tid_cond);
+ assert (i == 0);
+ i = pthread_mutex_unlock (&thread2_tid_mutex);
+ assert (i == 0);
+
+ /* Be sure the "t (tracing stop)" test can proceed for both
+ threads. */
+ timed_mutex_lock (&terminate_mutex);
+ i = pthread_mutex_unlock (&terminate_mutex);
+ assert (i == 0);
+
+ if (!thread2_sigusr1_hit)
+ {
+ fprintf (stderr, "Thread 2 signal SIGUSR1 not hit!\n");
+ exit (EXIT_FAILURE);
+ }
+ if (!thread2_sigusr2_hit)
+ {
+ fprintf (stderr, "Thread 2 signal SIGUSR2 not hit!\n");
+ exit (EXIT_FAILURE);
+ }
+
+ return NULL;
+}
+
+static const char *
+proc_string (const char *filename, const char *line)
+{
+ FILE *f;
+ static char buf[LINE_MAX];
+ size_t line_len = strlen (line);
+
+ f = fopen (filename, "r");
+ if (f == NULL)
+ {
+ fprintf (stderr, "fopen (\"%s\") for \"%s\": %s\n", filename, line,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+ while (errno = 0, fgets (buf, sizeof (buf), f))
+ {
+ char *s;
+
+ s = strchr (buf, '\n');
+ assert (s != NULL);
+ *s = 0;
+
+ if (strncmp (buf, line, line_len) != 0)
+ continue;
+
+ if (fclose (f))
+ {
+ fprintf (stderr, "fclose (\"%s\") for \"%s\": %s\n", filename, line,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+
+ return &buf[line_len];
+ }
+ if (errno != 0)
+ {
+ fprintf (stderr, "fgets (\"%s\": %s\n", filename, strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+ fprintf (stderr, "\"%s\": No line \"%s\" found.\n", filename, line);
+ exit (EXIT_FAILURE);
+}
+
+static unsigned long
+proc_ulong (const char *filename, const char *line)
+{
+ const char *s = proc_string (filename, line);
+ long retval;
+ char *end;
+
+ errno = 0;
+ retval = strtol (s, &end, 10);
+ if (retval < 0 || retval >= LONG_MAX || (end && *end))
+ {
+ fprintf (stderr, "\"%s\":\"%s\": %ld, %s\n", filename, line, retval,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+ return retval;
+}
+
+static void
+state_wait (pid_t process, const char *wanted)
+{
+ char *filename;
+ int i;
+ struct timespec start, now;
+ const char *state;
+
+ i = asprintf (&filename, "/proc/%lu/status", (unsigned long) process);
+ assert (i > 0);
+
+ i = clock_gettime (CLOCK_MONOTONIC, &start);
+ assert (i == 0);
+
+ do
+ {
+ state = proc_string (filename, "State:\t");
+
+ /* torvalds/linux-2.6.git 464763cf1c6df632dccc8f2f4c7e50163154a2c0
+ has changed "T (tracing stop)" to "t (tracing stop)". Make the GDB
+ testcase backward compatible with older Linux kernels. */
+ if (strcmp (state, "T (tracing stop)") == 0)
+ state = "t (tracing stop)";
+
+ if (strcmp (state, wanted) == 0)
+ {
+ free (filename);
+ return;
+ }
+
+ if (sched_yield ())
+ {
+ perror ("sched_yield()");
+ exit (EXIT_FAILURE);
+ }
+
+ i = clock_gettime (CLOCK_MONOTONIC, &now);
+ assert (i == 0);
+ assert (now.tv_sec >= start.tv_sec);
+ }
+ while (now.tv_sec - start.tv_sec < TIMEOUT);
+
+ fprintf (stderr, "Timed out waiting for PID %lu \"%s\" (now it is \"%s\")!\n",
+ (unsigned long) process, wanted, state);
+ exit (EXIT_FAILURE);
+}
+
+static volatile pid_t tracer = 0;
+static pthread_t thread1, thread2;
+
+static void
+cleanup (void)
+{
+ printf ("Resuming GDB PID %lu.\n", (unsigned long) tracer);
+
+ if (tracer)
+ {
+ int i;
+ int tracer_save = tracer;
+
+ tracer = 0;
+
+ i = kill (tracer_save, SIGCONT);
+ assert (i == 0);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ int standalone = 0;
+ struct sigaction act;
+
+ if (argc == 2 && strcmp (argv[1], "-s") == 0)
+ standalone = 1;
+ else
+ assert (argc == 1);
+
+ setbuf (stdout, NULL);
+
+ timed_mutex_lock (&thread1_tid_mutex);
+ timed_mutex_lock (&thread2_tid_mutex);
+
+ timed_mutex_lock (&terminate_mutex);
+
+ errno = 0;
+ memset (&act, 0, sizeof (act));
+ act.sa_sigaction = handler;
+ act.sa_flags = SA_RESTART | SA_SIGINFO;
+ i = sigemptyset (&act.sa_mask);
+ assert_perror (errno);
+ assert (i == 0);
+ i = sigaction (SIGUSR1, &act, NULL);
+ assert_perror (errno);
+ assert (i == 0);
+ i = sigaction (SIGUSR2, &act, NULL);
+ assert_perror (errno);
+ assert (i == 0);
+
+ i = pthread_create (&thread1, NULL, thread1_func, NULL);
+ assert (i == 0);
+
+ i = pthread_create (&thread2, NULL, thread2_func, NULL);
+ assert (i == 0);
+
+ if (!standalone)
+ {
+ tracer = proc_ulong ("/proc/self/status", "TracerPid:\t");
+ if (tracer == 0)
+ {
+ fprintf (stderr, "The testcase must be run by GDB!\n");
+ exit (EXIT_FAILURE);
+ }
+ if (tracer != getppid ())
+ {
+ fprintf (stderr, "The testcase parent must be our GDB tracer!\n");
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ /* SIGCONT our debugger in the case of our crash as we would deadlock
+ otherwise. */
+
+ atexit (cleanup);
+
+ printf ("Stopping GDB PID %lu.\n", (unsigned long) tracer);
+
+ if (tracer)
+ {
+ i = kill (tracer, SIGSTOP);
+ assert (i == 0);
+ state_wait (tracer, "T (stopped)");
+ }
+
+ /* Threads are now waiting at timed_mutex_lock (thread1_tid_mutex)
+ and so they could not trigger the signals before GDB is unstopped
+ later. Threads get resumed by the pthread_cond_wait below. Use
+ `while' loops for protection against spurious pthread_cond_wait
+ wakeups. */
+
+ printf ("Waiting till the threads initialize their TIDs.\n");
+
+ while (thread1_tid == 0)
+ {
+ i = pthread_cond_wait (&thread1_tid_cond, &thread1_tid_mutex);
+ assert (i == 0);
+ }
+
+ while (thread2_tid == 0)
+ {
+ i = pthread_cond_wait (&thread2_tid_cond, &thread2_tid_mutex);
+ assert (i == 0);
+ }
+
+ printf ("Thread 1 TID = %lu, thread 2 TID = %lu, PID = %lu.\n",
+ (unsigned long) thread1_tid, (unsigned long) thread2_tid,
+ (unsigned long) getpid ());
+
+ errno = 0;
+ i = tgkill (getpid (), thread1_tid, SIGUSR1);
+ assert_perror (errno);
+ assert (i == 0);
+ i = tgkill (getpid (), thread1_tid, SIGUSR2);
+ assert_perror (errno);
+ assert (i == 0);
+ i = tgkill (getpid (), thread2_tid, SIGUSR1);
+ assert_perror (errno);
+ assert (i == 0);
+ i = tgkill (getpid (), thread2_tid, SIGUSR2);
+ assert_perror (errno);
+ assert (i == 0);
+
+ printf ("Waiting till the threads are trapped by the signals.\n");
+
+ if (tracer)
+ {
+ /* s390x-unknown-linux-gnu will fail with "R (running)". */
+
+ state_wait (thread1_tid, "t (tracing stop)");
+
+ state_wait (thread2_tid, "t (tracing stop)");
+ }
+
+ cleanup ();
+
+ printf ("Joining the threads.\n");
+
+ i = pthread_mutex_unlock (&terminate_mutex);
+ assert (i == 0);
+
+ i = pthread_join (thread1, NULL);
+ assert (i == 0);
+
+ i = pthread_join (thread2, NULL);
+ assert (i == 0);
+
+ printf ("Exiting.\n"); /* break-at-exit */
+
+ return EXIT_SUCCESS;
+}
diff --git a/gdb/testsuite/gdb.threads/siginfo-threads.exp b/gdb/testsuite/gdb.threads/siginfo-threads.exp
new file mode 100644
index 0000000..f3f9c51
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/siginfo-threads.exp
@@ -0,0 +1,99 @@
+# Copyright 2010-2012 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/>.
+
+standard_testfile
+set executable ${testfile}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" \
+ ${binfile} \
+ executable \
+ [list debug additional_flags=-lrt]] != "" } {
+ return -1
+}
+
+clean_restart $testfile
+
+if ![runto_main] {
+ return -1
+}
+
+# `nostop noprint pass' could in some cases report false PASS due to
+# the (preempt 'handle') code path in linux-nat.c.
+
+gdb_test "handle SIGUSR1 stop print pass" \
+ "Signal\[ \t\]+Stop\[ \t\]+Print\[ \t\]+Pass to program\[ \t\]+Description\r\nSIGUSR1\[ \t\]+Yes\[ \t\]+Yes\[ \t\]+Yes\[ \t\].*"
+gdb_test "handle SIGUSR2 stop print pass" \
+ "Signal\[ \t\]+Stop\[ \t\]+Print\[ \t\]+Pass to program\[ \t\]+Description\r\nSIGUSR2\[ \t\]+Yes\[ \t\]+Yes\[ \t\]+Yes\[ \t\].*"
+
+gdb_breakpoint [gdb_get_line_number "break-at-exit"]
+
+set test "get pid"
+gdb_test_multiple "p getpid ()" $test {
+ -re " = (\[0-9\]+)\r\n$gdb_prompt $" {
+ set pid $expect_out(1,string)
+ pass $test
+ }
+}
+
+for {set sigcount 0} {$sigcount < 4} {incr sigcount} {
+ set test "catch signal $sigcount"
+ set sigusr ""
+ gdb_test_multiple "continue" $test {
+ -re "Program received signal SIGUSR(\[12\]), User defined signal \[12\]\\.\r\n.*\r\n$gdb_prompt $" {
+ set sigusr $expect_out(1,string)
+ pass $test
+ }
+ }
+ if {$sigusr == ""} {
+ return -1
+ }
+
+ set test "signal $sigcount si_signo"
+ if {$sigusr == 1} {
+ set signo 10
+ } else {
+ set signo 12
+ }
+ gdb_test_multiple {p $_siginfo.si_signo} $test {
+ -re " = $signo\r\n$gdb_prompt $" {
+ pass $test
+ }
+ -re "Attempt to extract a component of a value that is not a structure\\.\r\n$gdb_prompt $" {
+ unsupported $test
+ }
+ }
+
+ set test "signal $sigcount si_code is SI_TKILL"
+ gdb_test_multiple {p $_siginfo.si_code} $test {
+ -re " = -6\r\n$gdb_prompt $" {
+ pass $test
+ }
+ -re "Attempt to extract a component of a value that is not a structure\\.\r\n$gdb_prompt $" {
+ unsupported $test
+ }
+ }
+
+ set test "signal $sigcount si_pid"
+ gdb_test_multiple {p $_siginfo._sifields._kill.si_pid} $test {
+ -re " = $pid\r\n$gdb_prompt $" {
+ pass $test
+ }
+ -re "Attempt to extract a component of a value that is not a structure\\.\r\n$gdb_prompt $" {
+ unsupported $test
+ }
+ }
+}
+
+gdb_continue_to_breakpoint break-at-exit ".*break-at-exit.*"
diff --git a/gdb/testsuite/gdb.threads/sigstep-threads.c b/gdb/testsuite/gdb.threads/sigstep-threads.c
new file mode 100644
index 0000000..ca77fe4
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/sigstep-threads.c
@@ -0,0 +1,54 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2010-2012 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 <signal.h>
+
+#include <asm/unistd.h>
+#include <unistd.h>
+#define tgkill(tgid, tid, sig) syscall (__NR_tgkill, (tgid), (tid), (sig))
+#define gettid() syscall (__NR_gettid)
+
+static volatile int var;
+
+static void
+handler (int signo) /* step-0 */
+{ /* step-0 */
+ var++; /* step-1 */
+ tgkill (getpid (), gettid (), SIGUSR1); /* step-2 */
+}
+
+static void *
+start (void *arg)
+{
+ tgkill (getpid (), gettid (), SIGUSR1);
+ assert (0);
+ return NULL;
+}
+
+int
+main (void)
+{
+ pthread_t thread;
+
+ signal (SIGUSR1, handler);
+
+ pthread_create (&thread, NULL, start, NULL);
+ start (NULL); /* main-start */
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/sigstep-threads.exp b/gdb/testsuite/gdb.threads/sigstep-threads.exp
new file mode 100644
index 0000000..484ca37
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/sigstep-threads.exp
@@ -0,0 +1,73 @@
+# Copyright 2010-2012 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/>.
+
+standard_testfile
+set executable ${testfile}
+
+if { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ untested ${testfile}.exp
+ return -1
+}
+
+clean_restart $executable
+
+if ![runto_main] {
+ return -1;
+}
+
+# `noprint' would not test the full logic of GDB.
+gdb_test "handle SIGUSR1 nostop print pass" \
+ "\r\nSIGUSR1\[ \t\]+No\[ \t\]+Yes\[ \t\]+Yes\[ \t\].*"
+
+gdb_test_no_output "set scheduler-locking off"
+
+gdb_breakpoint [gdb_get_line_number "step-1"]
+gdb_test_no_output {set $step1=$bpnum}
+gdb_continue_to_breakpoint "step-1" ".* step-1 .*"
+gdb_test_no_output {disable $step1}
+
+# 1 as we are now stopped at the `step-1' label.
+set step_at 1
+for {set i 0} {$i < 100} {incr i} {
+ set test "step $i"
+ # Presume this step failed - as in the case of a timeout.
+ set failed 1
+ gdb_test_multiple "step" $test {
+ -re "\r\nProgram received signal SIGUSR1, User defined signal 1.\r\n" {
+ exp_continue -continue_timer
+ }
+ -re "step-(\[012\]).*\r\n$gdb_prompt $" {
+ set now $expect_out(1,string)
+ if {$step_at == 2 && $now == 1} {
+ set failed 0
+ } elseif {$step_at == 1 && $now == 2} {
+ set failed 0
+ # Continue over the re-signalling back to the handle entry.
+ gdb_test_no_output {enable $step1} ""
+ gdb_test "continue" " step-1 .*" ""
+ set now 1
+ gdb_test_no_output {disable $step1} ""
+ } else {
+ fail $test
+ }
+ set step_at $now
+ }
+ }
+ if $failed {
+ return
+ }
+}
+# We can never reliably say the racy problematic case has been tested.
+pass "step"
diff --git a/gdb/thread.c b/gdb/thread.c
index d361dd8..ff5220d 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -54,7 +54,7 @@ void _initialize_thread (void);
/* Prototypes for local functions. */
-static struct thread_info *thread_list = NULL;
+struct thread_info *thread_list = NULL;
static int highest_thread_num;
static void thread_command (char *tidstr, int from_tty);