aboutsummaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
authorPedro Alves <palves@redhat.com>2009-05-24 18:00:08 +0000
committerPedro Alves <palves@redhat.com>2009-05-24 18:00:08 +0000
commite58b0e63bb0a42e99f6fb1e6d697a1b29caa02c2 (patch)
tree0885c611c83e59c16a996ecc2aecb21f823bf028 /gdb
parente27d73f6b0fa7a7bb175b7b52b9fce5ff535e239 (diff)
downloadgdb-e58b0e63bb0a42e99f6fb1e6d697a1b29caa02c2.zip
gdb-e58b0e63bb0a42e99f6fb1e6d697a1b29caa02c2.tar.gz
gdb-e58b0e63bb0a42e99f6fb1e6d697a1b29caa02c2.tar.bz2
gdb/
* gdbthread.h (struct thread_info): New `pending_follow' field. * thread.c (new_thread): New function. (add_thread_silent): Use it. * breakpoint.c (internal_breakpoint_number): New global, moved from inside... (create_internal_breakpoint): ... this. (clone_momentary_breakpoint): New. * breakpoint.h (clone_momentary_breakpoint): Declare. * infrun.c (nullify_last_target_wait_ptid): Move declaration higher. (pending_follow): Delete. (follow_fork): Handle pending follow fork event here. Moved the preserving of thread stepping state here. (resume): Don't handle pending follow fork events here. Only install the inferior's terminal modes if we're about to resume it. (proceed): Handle possible pending follow fork events here. (init_wait_for_inferior): No need to clear pending_follow anymore, it's gone. (handle_inferior_event): Adjust to per-thread `pending_follow'. Call `follow_fork' to handle following the fork. If the follow-fork is cancelled, stop stepping. * linux-nat.c (linux_child_follow_fork): Adjust to per-thread `pending_follow' events. Remove code that handled preserving the thread stepping state. * inf-ptrace.c (inf_ptrace_follow_fork): Ditto. * inf-ttrace.c (inf_ttrace_follow_fork): Ditto. gdb/testsuite/ * gdb.threads/fork-thread-pending.c: New. * gdb.threads/fork-thread-pending.exp: New.
Diffstat (limited to 'gdb')
-rw-r--r--gdb/ChangeLog29
-rw-r--r--gdb/breakpoint.c40
-rw-r--r--gdb/breakpoint.h2
-rw-r--r--gdb/gdbthread.h5
-rw-r--r--gdb/inf-ptrace.c36
-rw-r--r--gdb/inf-ttrace.c44
-rw-r--r--gdb/infrun.c241
-rw-r--r--gdb/linux-nat.c37
-rw-r--r--gdb/testsuite/ChangeLog5
-rw-r--r--gdb/testsuite/gdb.threads/fork-thread-pending.c109
-rw-r--r--gdb/testsuite/gdb.threads/fork-thread-pending.exp128
-rw-r--r--gdb/thread.c37
12 files changed, 542 insertions, 171 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 70b5602..d392345d 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,32 @@
+2009-05-24 Pedro Alves <pedro@codesourcery.com>
+
+ * gdbthread.h (struct thread_info): New `pending_follow' field.
+ * thread.c (new_thread): New function.
+ (add_thread_silent): Use it.
+ * breakpoint.c (internal_breakpoint_number): New global, moved
+ from inside...
+ (create_internal_breakpoint): ... this.
+ (clone_momentary_breakpoint): New.
+ * breakpoint.h (clone_momentary_breakpoint): Declare.
+ * infrun.c (nullify_last_target_wait_ptid): Move declaration
+ higher.
+ (pending_follow): Delete.
+ (follow_fork): Handle pending follow fork event here. Moved the
+ preserving of thread stepping state here.
+ (resume): Don't handle pending follow fork events here. Only
+ install the inferior's terminal modes if we're about to resume it.
+ (proceed): Handle possible pending follow fork events here.
+ (init_wait_for_inferior): No need to clear pending_follow anymore,
+ it's gone.
+ (handle_inferior_event): Adjust to per-thread `pending_follow'.
+ Call `follow_fork' to handle following the fork. If the
+ follow-fork is cancelled, stop stepping.
+ * linux-nat.c (linux_child_follow_fork): Adjust to per-thread
+ `pending_follow' events. Remove code that handled preserving the
+ thread stepping state.
+ * inf-ptrace.c (inf_ptrace_follow_fork): Ditto.
+ * inf-ttrace.c (inf_ttrace_follow_fork): Ditto.
+
2009-05-24 Pierre Muller <muller@ics.u-strasbg.fr>
* symfile.c (add_shared_symbol_files_command): Remove
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index b5362e2..1235946 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -1456,10 +1456,11 @@ reattach_breakpoints (int pid)
return 0;
}
+static int internal_breakpoint_number = -1;
+
static struct breakpoint *
create_internal_breakpoint (CORE_ADDR address, enum bptype type)
{
- static int internal_breakpoint_number = -1;
struct symtab_and_line sal;
struct breakpoint *b;
@@ -5007,6 +5008,43 @@ set_momentary_breakpoint (struct symtab_and_line sal, struct frame_id frame_id,
return b;
}
+/* Make a deep copy of momentary breakpoint ORIG. Returns NULL if
+ ORIG is NULL. */
+
+struct breakpoint *
+clone_momentary_breakpoint (struct breakpoint *orig)
+{
+ struct breakpoint *copy;
+
+ /* If there's nothing to clone, then return nothing. */
+ if (orig == NULL)
+ return NULL;
+
+ copy = set_raw_breakpoint_without_location (orig->type);
+ copy->loc = allocate_bp_location (copy);
+ set_breakpoint_location_function (copy->loc);
+
+ copy->loc->requested_address = orig->loc->requested_address;
+ copy->loc->address = orig->loc->address;
+ copy->loc->section = orig->loc->section;
+
+ if (orig->source_file == NULL)
+ copy->source_file = NULL;
+ else
+ copy->source_file = xstrdup (orig->source_file);
+
+ copy->line_number = orig->line_number;
+ copy->frame_id = orig->frame_id;
+ copy->thread = orig->thread;
+
+ copy->enable_state = bp_enabled;
+ copy->disposition = disp_donttouch;
+ copy->number = internal_breakpoint_number--;
+
+ update_global_location_list_nothrow (0);
+ return copy;
+}
+
struct breakpoint *
set_momentary_breakpoint_at_pc (CORE_ADDR pc, enum bptype type)
{
diff --git a/gdb/breakpoint.h b/gdb/breakpoint.h
index 43c2f3f..4e672b5 100644
--- a/gdb/breakpoint.h
+++ b/gdb/breakpoint.h
@@ -696,6 +696,8 @@ extern struct breakpoint *set_momentary_breakpoint
extern struct breakpoint *set_momentary_breakpoint_at_pc
(CORE_ADDR pc, enum bptype type);
+extern struct breakpoint *clone_momentary_breakpoint (struct breakpoint *bpkt);
+
extern void set_ignore_count (int, int, int);
extern void set_default_breakpoint (int, CORE_ADDR, struct symtab *, int);
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 1833a9e..ccdaeb0 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -165,6 +165,11 @@ struct thread_info
next time inferior stops if it stops due to stepping. */
int step_multi;
+ /* This is used to remember when a fork or vfork event was caught by
+ a catchpoint, and thus the event is to be followed at the next
+ resume of the thread, and not immediately. */
+ struct target_waitstatus pending_follow;
+
/* Last signal that the inferior received (why it stopped). */
enum target_signal stop_signal;
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index 7076da7..89a37a6 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -46,20 +46,8 @@ inf_ptrace_follow_fork (struct target_ops *ops, int follow_child)
{
pid_t pid, fpid;
ptrace_state_t pe;
- struct thread_info *last_tp = NULL;
- /* FIXME: kettenis/20050720: This stuff should really be passed as
- an argument by our caller. */
- {
- ptid_t ptid;
- struct target_waitstatus status;
-
- get_last_target_status (&ptid, &status);
- gdb_assert (status.kind == TARGET_WAITKIND_FORKED);
-
- pid = ptid_get_pid (ptid);
- last_tp = find_thread_pid (ptid);
- }
+ pid = ptid_get_pid (inferior_ptid);
if (ptrace (PT_GET_PROCESS_STATE, pid,
(PTRACE_TYPE_ARG3)&pe, sizeof pe) == -1)
@@ -70,18 +58,9 @@ inf_ptrace_follow_fork (struct target_ops *ops, int follow_child)
if (follow_child)
{
- /* Copy user stepping state to the new inferior thread. */
- struct breakpoint *step_resume_breakpoint = last_tp->step_resume_breakpoint;
- CORE_ADDR step_range_start = last_tp->step_range_start;
- CORE_ADDR step_range_end = last_tp->step_range_end;
- struct frame_id step_frame_id = last_tp->step_frame_id;
struct inferior *parent_inf, *child_inf;
struct thread_info *tp;
- /* Otherwise, deleting the parent would get rid of this
- breakpoint. */
- last_tp->step_resume_breakpoint = NULL;
-
parent_inf = find_inferior_pid (pid);
/* Add the child. */
@@ -102,26 +81,15 @@ inf_ptrace_follow_fork (struct target_ops *ops, int follow_child)
/* Delete the parent. */
detach_inferior (pid);
- tp = add_thread_silent (inferior_ptid);
-
- tp->step_resume_breakpoint = step_resume_breakpoint;
- tp->step_range_start = step_range_start;
- tp->step_range_end = step_range_end;
- tp->step_frame_id = step_frame_id;
-
- /* Reset breakpoints in the child as appropriate. */
- follow_inferior_reset_breakpoints ();
+ add_thread_silent (inferior_ptid);
}
else
{
- inferior_ptid = pid_to_ptid (pid);
-
/* Breakpoints have already been detached from the child by
infrun.c. */
if (ptrace (PT_DETACH, fpid, (PTRACE_TYPE_ARG3)1, 0) == -1)
perror_with_name (("ptrace"));
- detach_inferior (pid);
}
return 0;
diff --git a/gdb/inf-ttrace.c b/gdb/inf-ttrace.c
index a4c4b99..ffec586 100644
--- a/gdb/inf-ttrace.c
+++ b/gdb/inf-ttrace.c
@@ -412,25 +412,13 @@ inf_ttrace_follow_fork (struct target_ops *ops, int follow_child)
pid_t pid, fpid;
lwpid_t lwpid, flwpid;
ttstate_t tts;
- struct thread_info *last_tp = NULL;
- struct breakpoint *step_resume_breakpoint = NULL;
- CORE_ADDR step_range_start = 0, step_range_end = 0;
- struct frame_id step_frame_id = null_frame_id;
-
- /* FIXME: kettenis/20050720: This stuff should really be passed as
- an argument by our caller. */
- {
- ptid_t ptid;
- struct target_waitstatus status;
-
- get_last_target_status (&ptid, &status);
- gdb_assert (status.kind == TARGET_WAITKIND_FORKED
- || status.kind == TARGET_WAITKIND_VFORKED);
-
- pid = ptid_get_pid (ptid);
- lwpid = ptid_get_lwp (ptid);
- last_tp = find_thread_pid (ptid);
- }
+ struct thread_info *tp = inferior_thread ();
+
+ gdb_assert (tp->pending_follow.kind == TARGET_WAITKIND_FORKED
+ || tp->pending_follow.kind == TARGET_WAITKIND_VFORKED);
+
+ pid = ptid_get_pid (inferior_ptid);
+ lwpid = ptid_get_lwp (inferior_ptid);
/* Get all important details that core GDB doesn't (and shouldn't)
know about. */
@@ -462,16 +450,6 @@ inf_ttrace_follow_fork (struct target_ops *ops, int follow_child)
parent_inf = find_inferior_pid (pid);
- /* Copy user stepping state to the new inferior thread. */
- step_resume_breakpoint = last_tp->step_resume_breakpoint;
- step_range_start = last_tp->step_range_start;
- step_range_end = last_tp->step_range_end;
- step_frame_id = last_tp->step_frame_id;
-
- /* Otherwise, deleting the parent would get rid of this
- breakpoint. */
- last_tp->step_resume_breakpoint = NULL;
-
inferior_ptid = ptid_build (fpid, flwpid, 0);
inf = add_inferior (fpid);
inf->attach_flag = parent_inf->attach_flag;
@@ -553,14 +531,6 @@ Detaching after fork from child process %ld.\n"), (long)fpid);
xmalloc (sizeof (struct inf_ttrace_private_thread_info));
memset (ti->private, 0,
sizeof (struct inf_ttrace_private_thread_info));
-
- ti->step_resume_breakpoint = step_resume_breakpoint;
- ti->step_range_start = step_range_start;
- ti->step_range_end = step_range_end;
- ti->step_frame_id = step_frame_id;
-
- /* Reset breakpoints in the child as appropriate. */
- follow_inferior_reset_breakpoints ();
}
return 0;
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 489f9d1..f9b34dd 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -83,6 +83,8 @@ static int prepare_to_proceed (int);
void _initialize_infrun (void);
+void nullify_last_target_wait_ptid (void);
+
/* When set, stop the 'step' command if we enter a function which has
no line number information. The normal behavior is that we step
over such function. */
@@ -255,21 +257,6 @@ void init_thread_stepping_state (struct thread_info *tss);
void init_infwait_state (void);
-/* This is used to remember when a fork or vfork event was caught by a
- catchpoint, and thus the event is to be followed at the next resume
- of the inferior, and not immediately. */
-static struct
-{
- enum target_waitkind kind;
- struct
- {
- ptid_t parent_pid;
- ptid_t child_pid;
- }
- fork_event;
-}
-pending_follow;
-
static const char follow_fork_mode_child[] = "child";
static const char follow_fork_mode_parent[] = "parent";
@@ -290,12 +277,157 @@ Debugger response to a program call of fork or vfork is \"%s\".\n"),
}
+/* Tell the target to follow the fork we're stopped at. Returns true
+ if the inferior should be resumed; false, if the target for some
+ reason decided it's best not to resume. */
+
static int
follow_fork (void)
{
int follow_child = (follow_fork_mode_string == follow_fork_mode_child);
+ int should_resume = 1;
+ struct thread_info *tp;
+
+ /* Copy user stepping state to the new inferior thread. FIXME: the
+ followed fork child thread should have a copy of most of the
+ parent thread structure's run control related fields, not just
+ these. */
+ struct breakpoint *step_resume_breakpoint;
+ CORE_ADDR step_range_start;
+ CORE_ADDR step_range_end;
+ struct frame_id step_frame_id;
+
+ if (!non_stop)
+ {
+ ptid_t wait_ptid;
+ struct target_waitstatus wait_status;
+
+ /* Get the last target status returned by target_wait(). */
+ get_last_target_status (&wait_ptid, &wait_status);
+
+ /* If not stopped at a fork event, then there's nothing else to
+ do. */
+ if (wait_status.kind != TARGET_WAITKIND_FORKED
+ && wait_status.kind != TARGET_WAITKIND_VFORKED)
+ return 1;
+
+ /* Check if we switched over from WAIT_PTID, since the event was
+ reported. */
+ if (!ptid_equal (wait_ptid, minus_one_ptid)
+ && !ptid_equal (inferior_ptid, wait_ptid))
+ {
+ /* We did. Switch back to WAIT_PTID thread, to tell the
+ target to follow it (in either direction). We'll
+ afterwards refuse to resume, and inform the user what
+ happened. */
+ switch_to_thread (wait_ptid);
+ should_resume = 0;
+ }
+ }
+
+ tp = inferior_thread ();
+
+ /* If there were any forks/vforks that were caught and are now to be
+ followed, then do so now. */
+ switch (tp->pending_follow.kind)
+ {
+ case TARGET_WAITKIND_FORKED:
+ case TARGET_WAITKIND_VFORKED:
+ {
+ ptid_t parent, child;
+
+ /* If the user did a next/step, etc, over a fork call,
+ preserve the stepping state in the fork child. */
+ if (follow_child && should_resume)
+ {
+ step_resume_breakpoint
+ = clone_momentary_breakpoint (tp->step_resume_breakpoint);
+ step_range_start = tp->step_range_start;
+ step_range_end = tp->step_range_end;
+ step_frame_id = tp->step_frame_id;
+
+ /* For now, delete the parent's sr breakpoint, otherwise,
+ parent/child sr breakpoints are considered duplicates,
+ and the child version will not be installed. Remove
+ this when the breakpoints module becomes aware of
+ inferiors and address spaces. */
+ delete_step_resume_breakpoint (tp);
+ tp->step_range_start = 0;
+ tp->step_range_end = 0;
+ tp->step_frame_id = null_frame_id;
+ }
+
+ parent = inferior_ptid;
+ child = tp->pending_follow.value.related_pid;
+
+ /* Tell the target to do whatever is necessary to follow
+ either parent or child. */
+ if (target_follow_fork (follow_child))
+ {
+ /* Target refused to follow, or there's some other reason
+ we shouldn't resume. */
+ should_resume = 0;
+ }
+ else
+ {
+ /* This pending follow fork event is now handled, one way
+ or another. The previous selected thread may be gone
+ from the lists by now, but if it is still around, need
+ to clear the pending follow request. */
+ tp = find_thread_pid (parent);
+ if (tp)
+ tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
+
+ /* This makes sure we don't try to apply the "Switched
+ over from WAIT_PID" logic above. */
+ nullify_last_target_wait_ptid ();
+
+ /* If we followed the child, switch to it... */
+ if (follow_child)
+ {
+ switch_to_thread (child);
+
+ /* ... and preserve the stepping state, in case the
+ user was stepping over the fork call. */
+ if (should_resume)
+ {
+ tp = inferior_thread ();
+ tp->step_resume_breakpoint = step_resume_breakpoint;
+ tp->step_range_start = step_range_start;
+ tp->step_range_end = step_range_end;
+ tp->step_frame_id = step_frame_id;
+ }
+ else
+ {
+ /* If we get here, it was because we're trying to
+ resume from a fork catchpoint, but, the user
+ has switched threads away from the thread that
+ forked. In that case, the resume command
+ issued is most likely not applicable to the
+ child, so just warn, and refuse to resume. */
+ warning (_("\
+Not resuming: switched threads before following fork child.\n"));
+ }
+
+ /* Reset breakpoints in the child as appropriate. */
+ follow_inferior_reset_breakpoints ();
+ }
+ else
+ switch_to_thread (parent);
+ }
+ }
+ break;
+ case TARGET_WAITKIND_SPURIOUS:
+ /* Nothing to follow. */
+ break;
+ default:
+ internal_error (__FILE__, __LINE__,
+ "Unexpected pending_follow.kind %d\n",
+ tp->pending_follow.kind);
+ break;
+ }
- return target_follow_fork (follow_child);
+ return should_resume;
}
void
@@ -987,8 +1119,6 @@ resume (int step, enum target_signal sig)
{
int should_resume = 1;
struct cleanup *old_cleanups = make_cleanup (resume_cleanups, 0);
-
- /* Note that these must be reset if we follow a fork below. */
struct regcache *regcache = get_current_regcache ();
struct gdbarch *gdbarch = get_regcache_arch (regcache);
struct thread_info *tp = inferior_thread ();
@@ -1058,31 +1188,6 @@ a command like `return' or `jump' to continue execution."));
if (step)
step = maybe_software_singlestep (gdbarch, pc);
- /* If there were any forks/vforks/execs that were caught and are
- now to be followed, then do so. */
- switch (pending_follow.kind)
- {
- case TARGET_WAITKIND_FORKED:
- case TARGET_WAITKIND_VFORKED:
- pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
- if (follow_fork ())
- should_resume = 0;
-
- /* Following a child fork will change our notion of current
- thread. */
- tp = inferior_thread ();
- regcache = get_current_regcache ();
- gdbarch = get_regcache_arch (regcache);
- pc = regcache_read_pc (regcache);
- break;
-
- default:
- break;
- }
-
- /* Install inferior's terminal modes. */
- target_terminal_inferior ();
-
if (should_resume)
{
ptid_t resume_ptid;
@@ -1164,6 +1269,9 @@ a command like `return' or `jump' to continue execution."));
displaced_step_dump_bytes (gdb_stdlog, buf, sizeof (buf));
}
+ /* Install inferior's terminal modes. */
+ target_terminal_inferior ();
+
/* Avoid confusing the next resume, if the next stop/resume
happens to apply to another thread. */
tp->stop_signal = TARGET_SIGNAL_0;
@@ -1305,12 +1413,26 @@ prepare_to_proceed (int step)
void
proceed (CORE_ADDR addr, enum target_signal siggnal, int step)
{
- struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
+ struct regcache *regcache;
+ struct gdbarch *gdbarch;
struct thread_info *tp;
- CORE_ADDR pc = regcache_read_pc (regcache);
+ CORE_ADDR pc;
int oneproc = 0;
+ /* If we're stopped at a fork/vfork, follow the branch set by the
+ "set follow-fork-mode" command; otherwise, we'll just proceed
+ resuming the current thread. */
+ if (!follow_fork ())
+ {
+ /* The target for some reason decided not to resume. */
+ normal_stop ();
+ return;
+ }
+
+ regcache = get_current_regcache ();
+ gdbarch = get_regcache_arch (regcache);
+ pc = regcache_read_pc (regcache);
+
if (step > 0)
step_start_function = find_pc_function (pc);
if (step < 0)
@@ -1517,9 +1639,6 @@ init_wait_for_inferior (void)
breakpoint_init_inferior (inf_starting);
- /* The first resume is not following a fork/vfork/exec. */
- pending_follow.kind = TARGET_WAITKIND_SPURIOUS; /* I.e., none. */
-
clear_proceed_status ();
stepping_past_singlestep_breakpoint = 0;
@@ -1698,8 +1817,6 @@ infrun_thread_stop_requested (ptid_t ptid)
iterate_over_threads (infrun_thread_stop_requested_callback, &ptid);
}
-void nullify_last_target_wait_ptid (void);
-
static void
infrun_thread_thread_exit (struct thread_info *tp, int silent)
{
@@ -2407,10 +2524,6 @@ handle_inferior_event (struct execution_control_state *ecs)
case TARGET_WAITKIND_VFORKED:
if (debug_infrun)
fprintf_unfiltered (gdb_stdlog, "infrun: TARGET_WAITKIND_FORKED\n");
- pending_follow.kind = ecs->ws.kind;
-
- pending_follow.fork_event.parent_pid = ecs->ptid;
- pending_follow.fork_event.child_pid = ecs->ws.value.related_pid;
if (!ptid_equal (ecs->ptid, inferior_ptid))
{
@@ -2439,6 +2552,11 @@ handle_inferior_event (struct execution_control_state *ecs)
detach_breakpoints (child_pid);
}
+ /* In case the event is caught by a catchpoint, remember that
+ the event is to be followed at the next resume of the thread,
+ and not immediately. */
+ ecs->event_thread->pending_follow = ecs->ws;
+
stop_pc = regcache_read_pc (get_thread_regcache (ecs->ptid));
ecs->event_thread->stop_bpstat = bpstat_stop_status (stop_pc, ecs->ptid);
@@ -2448,8 +2566,19 @@ handle_inferior_event (struct execution_control_state *ecs)
/* If no catchpoint triggered for this, then keep going. */
if (ecs->random_signal)
{
+ int should_resume;
+
ecs->event_thread->stop_signal = TARGET_SIGNAL_0;
- keep_going (ecs);
+
+ should_resume = follow_fork ();
+
+ ecs->event_thread = inferior_thread ();
+ ecs->ptid = inferior_ptid;
+
+ if (should_resume)
+ keep_going (ecs);
+ else
+ stop_stepping (ecs);
return;
}
ecs->event_thread->stop_signal = TARGET_SIGNAL_TRAP;
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index 9057442..f93ea9f 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -575,19 +575,17 @@ static int
linux_child_follow_fork (struct target_ops *ops, int follow_child)
{
sigset_t prev_mask;
- ptid_t last_ptid;
- struct target_waitstatus last_status;
int has_vforked;
int parent_pid, child_pid;
block_child_signals (&prev_mask);
- get_last_target_status (&last_ptid, &last_status);
- has_vforked = (last_status.kind == TARGET_WAITKIND_VFORKED);
- parent_pid = ptid_get_lwp (last_ptid);
+ has_vforked = (inferior_thread ()->pending_follow.kind
+ == TARGET_WAITKIND_VFORKED);
+ parent_pid = ptid_get_lwp (inferior_ptid);
if (parent_pid == 0)
- parent_pid = ptid_get_pid (last_ptid);
- child_pid = PIDGET (last_status.value.related_pid);
+ parent_pid = ptid_get_pid (inferior_ptid);
+ child_pid = PIDGET (inferior_thread ()->pending_follow.value.related_pid);
if (! follow_child)
{
@@ -625,7 +623,7 @@ linux_child_follow_fork (struct target_ops *ops, int follow_child)
/* Add process to GDB's tables. */
child_inf = add_inferior (child_pid);
- parent_inf = find_inferior_pid (GET_PID (last_ptid));
+ parent_inf = current_inferior ();
child_inf->attach_flag = parent_inf->attach_flag;
copy_terminal_info (child_inf, parent_inf);
@@ -692,21 +690,9 @@ linux_child_follow_fork (struct target_ops *ops, int follow_child)
}
else
{
- struct thread_info *last_tp = find_thread_pid (last_ptid);
struct thread_info *tp;
- char child_pid_spelling[40];
struct inferior *parent_inf, *child_inf;
- /* Copy user stepping state to the new inferior thread. */
- struct breakpoint *step_resume_breakpoint = last_tp->step_resume_breakpoint;
- CORE_ADDR step_range_start = last_tp->step_range_start;
- CORE_ADDR step_range_end = last_tp->step_range_end;
- struct frame_id step_frame_id = last_tp->step_frame_id;
-
- /* Otherwise, deleting the parent would get rid of this
- breakpoint. */
- last_tp->step_resume_breakpoint = NULL;
-
/* Before detaching from the parent, remove all breakpoints from it. */
remove_breakpoints ();
@@ -723,7 +709,7 @@ linux_child_follow_fork (struct target_ops *ops, int follow_child)
child_inf = add_inferior (child_pid);
- parent_inf = find_inferior_pid (GET_PID (last_ptid));
+ parent_inf = current_inferior ();
child_inf->attach_flag = parent_inf->attach_flag;
copy_terminal_info (child_inf, parent_inf);
@@ -772,15 +758,6 @@ linux_child_follow_fork (struct target_ops *ops, int follow_child)
linux_nat_switch_fork (inferior_ptid);
check_for_thread_db ();
-
- tp = inferior_thread ();
- tp->step_resume_breakpoint = step_resume_breakpoint;
- tp->step_range_start = step_range_start;
- tp->step_range_end = step_range_end;
- tp->step_frame_id = step_frame_id;
-
- /* Reset breakpoints in the child as appropriate. */
- follow_inferior_reset_breakpoints ();
}
restore_child_signals_mask (&prev_mask);
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index c65edbe..5fabccc 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,8 @@
+2009-05-24 Pedro Alves <pedro@codesourcery.com>
+
+ * gdb.threads/fork-thread-pending.c: New.
+ * gdb.threads/fork-thread-pending.exp: New.
+
2009-05-21 Jan Kratochvil <jan.kratochvil@redhat.com>
* gdb.dwarf2/dw2-strp.exp (p a_string2, ptype a_string2): New.
diff --git a/gdb/testsuite/gdb.threads/fork-thread-pending.c b/gdb/testsuite/gdb.threads/fork-thread-pending.c
new file mode 100644
index 0000000..34ea52d
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/fork-thread-pending.c
@@ -0,0 +1,109 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2008, 2009 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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define NUMTHREADS 10
+
+volatile int done = 0;
+
+static void *
+start (void *arg)
+{
+ while (!done)
+ usleep (100);
+ assert (0);
+ return arg;
+}
+
+void *
+thread_function (void *arg)
+{
+ int x = * (int *) arg;
+
+ printf ("Thread <%d> executing\n", x);
+
+ while (!done)
+ usleep (100);
+
+ return NULL;
+}
+
+void *
+thread_forker (void *arg)
+{
+ int x = * (int *) arg;
+ pid_t pid;
+ int rv;
+ int i;
+ pthread_t thread;
+
+ printf ("Thread forker <%d> executing\n", x);
+
+ switch ((pid = fork ()))
+ {
+ case -1:
+ assert (0);
+ default:
+ wait (&rv);
+ done = 1;
+ break;
+ case 0:
+ i = pthread_create (&thread, NULL, start, NULL);
+ assert (i == 0);
+ i = pthread_join (thread, NULL);
+ assert (i == 0);
+
+ assert (0);
+ }
+
+ return NULL;
+}
+
+int
+main (void)
+{
+ pthread_t threads[NUMTHREADS];
+ int args[NUMTHREADS];
+ int i, j;
+
+ /* Create a few threads that do mostly nothing, and then one that
+ forks. */
+ for (j = 0; j < NUMTHREADS - 1; ++j)
+ {
+ args[j] = j;
+ pthread_create (&threads[j], NULL, thread_function, &args[j]);
+ }
+
+ args[j] = j;
+ pthread_create (&threads[j], NULL, thread_forker, &args[j]);
+
+ for (j = 0; j < NUMTHREADS; ++j)
+ {
+ pthread_join (threads[j], NULL);
+ }
+
+ return 0;
+}
diff --git a/gdb/testsuite/gdb.threads/fork-thread-pending.exp b/gdb/testsuite/gdb.threads/fork-thread-pending.exp
new file mode 100644
index 0000000..6bc866b
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/fork-thread-pending.exp
@@ -0,0 +1,128 @@
+# Copyright (C) 2009 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/>.
+
+# There's no support for `set follow-fork-mode' in the remote
+# protocol.
+if { [is_remote target] } {
+ return 0
+}
+
+# Only GNU/Linux is known to support `set follow-fork-mode child'.
+#
+if { ! [istarget "*-*-linux*"] } {
+ return 0
+}
+
+set testfile fork-thread-pending
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+ return -1
+}
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+
+gdb_load ${binfile}
+if ![runto_main] then {
+ fail "Can't run to main"
+ return 0
+}
+
+gdb_test "set follow-fork-mode child" "" "1, set follow-fork-mode child"
+gdb_test "catch fork" "Catchpoint \[0-9\]* \\(fork\\)" "1, insert fork catchpoint"
+gdb_breakpoint "start" "" "1, set breakpoint at start"
+
+gdb_test "continue" "Catchpoint.*" "1, get to the fork event"
+
+gdb_test "info threads" " Thread .* Thread .* Thread .* Thread .*" "1, multiple threads found"
+
+gdb_test "thread 2" "" "1, switched away from event thread"
+
+gdb_test "continue" "Not resuming.*" "1, refused to resume"
+
+set test "1, followed to the child, found one thread"
+gdb_test_multiple "info threads" "metest" {
+ -re " Thread .* Thread .*$gdb_prompt $" {
+ fail "$test"
+ }
+ -re " Thread .*$gdb_prompt $" {
+ pass "$test"
+ }
+ -re "$gdb_prompt $" {
+ fail "$test (unknown output)"
+ }
+ timeout {
+ fail "$test (timeout)"
+ }
+}
+
+gdb_test "continue" "Breakpoint 3, start.*" "1, get to the spawned thread in fork child"
+
+set test "1, followed to the child, found two threads"
+gdb_test_multiple "info threads" "$test" {
+ -re " Thread .* Thread .* Thread .*$gdb_prompt $" {
+ fail "$test"
+ }
+ -re " Thread .* Thread .*$gdb_prompt $" {
+ pass "$test"
+ }
+ -re "$gdb_prompt $" {
+ fail "$test (unknown output)"
+ }
+ timeout {
+ fail "$test (timeout)"
+ }
+}
+
+# Start over, but this time, don't switch away from the fork event thread.
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+
+gdb_load ${binfile}
+if ![runto_main] then {
+ fail "Can't run to main"
+ return 0
+}
+
+gdb_test "set follow-fork-mode child" "" "2, set follow-fork-mode child"
+gdb_test "catch fork" "Catchpoint \[0-9\]* \\(fork\\)" "2, insert fork catchpoint"
+gdb_breakpoint "start"
+
+gdb_test "continue" "Catchpoint.*" "2, get to the fork event"
+
+gdb_test "info threads" " Thread .* Thread .* Thread .* Thread .*" "2, multiple threads found"
+
+gdb_test "continue" "Breakpoint 3, start.*" "2, get to the spawned thread in fork child"
+
+set test "2, followed to the child, found two threads"
+gdb_test_multiple "info threads" "$test" {
+ -re " Thread .* Thread .* Thread .*$gdb_prompt $" {
+ fail "$test"
+ }
+ -re " Thread .* Thread .*$gdb_prompt $" {
+ pass "$test"
+ }
+ -re "$gdb_prompt $" {
+ fail "$test (unknown output)"
+ }
+ timeout {
+ fail "$test (timeout)"
+ }
+}
diff --git a/gdb/thread.c b/gdb/thread.c
index 95f265a..7b2c6bb 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -141,6 +141,28 @@ init_thread_list (void)
thread_list = NULL;
}
+/* Allocate a new thread with target id PTID and add it to the thread
+ list. */
+
+static struct thread_info *
+new_thread (ptid_t ptid)
+{
+ struct thread_info *tp;
+
+ tp = xcalloc (1, sizeof (*tp));
+
+ tp->ptid = ptid;
+ tp->num = ++highest_thread_num;
+ tp->next = thread_list;
+ thread_list = tp;
+
+ /* Nothing to follow yet. */
+ tp->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
+ tp->state_ = THREAD_STOPPED;
+
+ return tp;
+}
+
struct thread_info *
add_thread_silent (ptid_t ptid)
{
@@ -162,12 +184,7 @@ add_thread_silent (ptid_t ptid)
if (ptid_equal (inferior_ptid, ptid))
{
- tp = xmalloc (sizeof (*tp));
- memset (tp, 0, sizeof (*tp));
- tp->ptid = minus_one_ptid;
- tp->num = ++highest_thread_num;
- tp->next = thread_list;
- thread_list = tp;
+ tp = new_thread (ptid);
/* Make switch_to_thread not read from the thread. */
tp->state_ = THREAD_EXITED;
@@ -191,13 +208,7 @@ add_thread_silent (ptid_t ptid)
delete_thread (ptid);
}
- tp = (struct thread_info *) xmalloc (sizeof (*tp));
- memset (tp, 0, sizeof (*tp));
- tp->ptid = ptid;
- tp->num = ++highest_thread_num;
- tp->next = thread_list;
- thread_list = tp;
-
+ tp = new_thread (ptid);
observer_notify_new_thread (tp);
return tp;