Re: [PATCH] Do not respawn signals, take 2.

Pedro Alves
2012-06-28T17:28:13Z

On 06/22/2012 08:18 PM, Jan Kratochvil wrote:
> On Fri, 22 Jun 2012 16:55:25 +0200, Pedro Alves wrote:
>> @@ -1950,9 +1954,24 @@ resume_lwp (struct lwp_info *lp, int step)
>> }
>>
>> 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))
>
> This signal_pass_state check seems redundant to me. At least
> remote.c append_pending_thread_resumptions does not do the check and infrun.c
> already seems to pre-clear it:
> /* If this signal should not be seen by program,
> give it zero. Used for debugging signals. */
> else if (!signal_program[tp->suspend.stop_signal])
> tp->suspend.stop_signal = GDB_SIGNAL_0;

Yeah. I missed doing it on remote.c as well, and actually testing on ARM
GNU/Linux showed regressions, revealing the mistake. (I also forgot to skip
inferior_ptid in remote.c, leading to duplicate vCont actions in the same packet.)

There are several other paths that don't clear stop_signal, like paths that
handle an internal SIGTRAP (software single-step breakpoints, etc.).
These are relying on the fact that keep_going/proceed check the
pass state before passing down stop_signal to 'resume'. Even if we made
all those paths clear stop_signal, I still think it is safer not to go that direction,
in that, e.g., if some error is thrown while handling an event, before we have a
chance of clearing stop_signal, the next resume (after the user fixed up
whatever caused the error) still works correctly. If we'd rely on
stop_signal being cleared, we'd risk inadvertently passing a signal to the
inferior and killing it on on that next resume (that's what was happening with
the previous patch; we'd kill the inferior with SIGTRAP on some cases). Also it seems
a little better to not drop the "last stop signal" information as well if we can, say
if we want to print the signal a thread last stopped with on "info threads" output
or some such.

>
>
>> + signo = thread->suspend.stop_signal;
>> + thread->suspend.stop_signal = GDB_SIGNAL_0;
>> + }
>> + }
>> +
>> + resume_lwp (lp, 0, signo);
>> return 0;
>> }
>>
> [...]
>> @@ -2856,6 +2875,8 @@ stop_wait_callback (struct lwp_info *lp, void *data)
>> {
>> int status;
>>
>> + gdb_assert (lp->resumed);
>> +
>> status = wait_lwp (lp);
>> if (status == 0)
>> return 0;
>
> This assertion happens sometimes.
> https://bugzilla.redhat.com/show_bug.cgi?id=808404
>
> The assertion seems right to me,

> IIRC you also concluded it indicates a Linux
> kernel bug where sometimes SIGSTOP is not generated by PTRACE_ATTACH.

Yeah, somebody reported a similar weird issue a while ago on irc.
It sounded like his kernel was deeply confused.

In the example in that bug report, gdb hangs because the kernel doesn't
report a SIGSTOP, and then to try to work around it, the user killed the process
with some other signal (efectively unblocking the waitpid call gdb is stuck on), and
that somehow caused gdb to hit that assertion. I can't figure out from reading
the code how the assertion ended up triggering.

> Keeping the assertion may give more clue in bugreports when the Linux kernel
> bug happens but AFAIK the FSF GDB policy is against such "more assertion for
> better bugreports" policy. But it is also true I do not know what GDB will do
> if the kernel bug happens and this assertion is not in place.

Yeah.

> Therefore for FSF GDB the general approach is it should be removed.

I've removed the assertion.

Thanks.

On 06/22/2012 04:11 PM, Pedro Alves wrote:
> Anyone with easy access to GNU/Linux ARM that could test this? Otherwise
> I'll try to find a machine.

I found a couple ARM machines. This revision of the patch showed no regressions on
{x86_64,ARM} {native,gdbserver} GNU/Linux, and the new tests fully pass. I've
also done the promised re-indent in infrun.c.

I've checked it in.

gdb/
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.

gdb/testsuite/
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.
---
gdb/gdbthread.h | 7
gdb/infrun.c | 372 ++++++++++----------
gdb/linux-nat.c | 208 +++--------
gdb/remote.c | 26 +
gdb/target.h | 14 +
gdb/testsuite/gdb.threads/siginfo-threads.c | 457 +++++++++++++++++++++++++
gdb/testsuite/gdb.threads/siginfo-threads.exp | 99 +++++
gdb/testsuite/gdb.threads/sigstep-threads.c | 54 +++
gdb/testsuite/gdb.threads/sigstep-threads.exp | 73 ++++
gdb/thread.c | 2
10 files changed, 968 insertions(+), 344 deletions(-)
create mode 100644 gdb/testsuite/gdb.threads/siginfo-threads.c
create mode 100644 gdb/testsuite/gdb.threads/siginfo-threads.exp
create mode 100644 gdb/testsuite/gdb.threads/sigstep-threads.c
create mode 100644 gdb/testsuite/gdb.threads/sigstep-threads.exp

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/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);