summaryrefslogtreecommitdiff
path: root/gdb/gdbserver
diff options
context:
space:
mode:
authorDon Breazeal <donb@codesourcery.com>2015-05-12 09:52:43 -0700
committerDon Breazeal <donb@codesourcery.com>2015-05-12 09:52:43 -0700
commitde0d863ec3fda88e488cee568f943c7998b68862 (patch)
treeba0c782f7e34cc80b37f9a704f71e897364f364c /gdb/gdbserver
parentddcbc3975fffffdb5bb3bfb05b1f0ef3d67be3ee (diff)
downloadbinutils-gdb-de0d863ec3fda88e488cee568f943c7998b68862.tar.gz
Extended-remote Linux follow fork
This patch implements basic support for follow-fork and detach-on-fork on extended-remote Linux targets. Only 'fork' is supported in this patch; 'vfork' support is added n a subsequent patch. This patch depends on the previous patches in the patch series. Sufficient extended-remote functionality has been implemented here to pass gdb.base/multi-forks.exp, as well as gdb.base/foll-fork.exp with the catchpoint tests commented out. Some other fork tests fail with this patch because it doesn't provide the architecture support needed for watchpoint inheritance or fork catchpoints. The implementation follows the same general structure as for the native implementation as much as possible. This implementation includes: * enabling fork events in linux-low.c in initialize_low and linux_enable_extended_features * handling fork events in gdbserver/linux-low.c:handle_extended_wait - when a fork event occurs in gdbserver, we must do the full creation of the new process, thread, lwp, and breakpoint lists. This is required whether or not the new child is destined to be detached-on-fork, because GDB will make target calls that require all the structures. In particular we need the breakpoint lists in order to remove the breakpoints from a detaching child. If we are not detaching the child we will need all these structures anyway. - as part of this event handling we store the target_waitstatus in a new member of the parent lwp_info structure, 'waitstatus'. This is used to store extended event information for reporting to GDB. - handle_extended_wait is given a return value, denoting whether the handled event should be reported to GDB. Previously it had only handled clone events, which were never reported. * using a new predicate in gdbserver to control handling of the fork event (and eventually all extended events) in linux_wait_1. The predicate, extended_event_reported, checks a target_waitstatus.kind for an extended ptrace event. * implementing a new RSP 'T' Stop Reply Packet stop reason: "fork", in gdbserver/remote-utils.c and remote.c. * implementing new target and RSP support for target_follow_fork with target extended-remote. (The RSP components were actually defined in patch 1, but they see their first use here). - remote target routine remote_follow_fork, which just sends the 'D;pid' detach packet to detach the new fork child cleanly. We can't just call target_detach because the data structures for the forked child have not been allocated on the host side. Tested on x64 Ubuntu Lucid, native, remote, extended-remote. gdb/gdbserver/ChangeLog: * linux-low.c (handle_extended_wait): Implement return value, rename argument 'event_child' to 'event_lwp', handle PTRACE_EVENT_FORK, call internal_error for unrecognized event. (linux_low_ptrace_options): New function. (linux_low_filter_event): Call linux_low_ptrace_options, use different argument fo linux_enable_event_reporting, use return value from handle_extended_wait. (extended_event_reported): New function. (linux_wait_1): Call extended_event_reported and set status to report fork events. (linux_write_memory): Add pid to debug message. (reset_lwp_ptrace_options_callback): New function. (linux_handle_new_gdb_connection): New function. (linux_target_ops): Initialize new structure member. * linux-low.h (struct lwp_info) <waitstatus>: New member. * lynx-low.c: Initialize new structure member. * remote-utils.c (prepare_resume_reply): Implement stop reason "fork" for "T" stop message. * server.c (handle_query): Call handle_new_gdb_connection. * server.h (report_fork_events): Declare global flag. * target.h (struct target_ops) <handle_new_gdb_connection>: New member. (target_handle_new_gdb_connection): New macro. * win32-low.c: Initialize new structure member. gdb/ChangeLog: * linux-nat.c (linux_nat_ptrace_options): New function. (linux_init_ptrace, wait_lwp, linux_nat_filter_event): Call linux_nat_ptrace_options and use different argument to linux_enable_event_reporting. (_initialize_linux_nat): Delete call to linux_ptrace_set_additional_flags. * nat/linux-ptrace.c (current_ptrace_options): Rename to supported_ptrace_options. (additional_flags): Delete variable. (linux_check_ptrace_features): Use supported_ptrace_options. (linux_test_for_tracesysgood, linux_test_for_tracefork): Likewise, and remove additional_flags check. (linux_enable_event_reporting): Change 'attached' argument to 'options'. Use supported_ptrace_options. (ptrace_supports_feature): Change comment. Use supported_ptrace_options. (linux_ptrace_set_additional_flags): Delete function. * nat/linux-ptrace.h (linux_ptrace_set_additional_flags): Delete function prototype. * remote.c (remote_fork_event_p): New function. (remote_detach_pid): New function. (remote_detach_1): Call remote_detach_pid, don't mourn inferior if doing detach-on-fork. (remote_follow_fork): New function. (remote_parse_stop_reply): Handle new "T" stop reason "fork". (remote_pid_to_str): Print "process" strings for pid/0/0 ptids. (init_extended_remote_ops): Initialize to_follow_fork.
Diffstat (limited to 'gdb/gdbserver')
-rw-r--r--gdb/gdbserver/ChangeLog27
-rw-r--r--gdb/gdbserver/linux-low.c180
-rw-r--r--gdb/gdbserver/linux-low.h4
-rw-r--r--gdb/gdbserver/lynx-low.c1
-rw-r--r--gdb/gdbserver/remote-utils.c14
-rw-r--r--gdb/gdbserver/server.c3
-rw-r--r--gdb/gdbserver/server.h1
-rw-r--r--gdb/gdbserver/target.h7
-rw-r--r--gdb/gdbserver/win32-low.c1
9 files changed, 223 insertions, 15 deletions
diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog
index 73788ece871..0ef695c43ac 100644
--- a/gdb/gdbserver/ChangeLog
+++ b/gdb/gdbserver/ChangeLog
@@ -1,5 +1,32 @@
2015-05-12 Don Breazeal <donb@codesourcery.com>
+ * linux-low.c (handle_extended_wait): Implement return value,
+ rename argument 'event_child' to 'event_lwp', handle
+ PTRACE_EVENT_FORK, call internal_error for unrecognized event.
+ (linux_low_ptrace_options): New function.
+ (linux_low_filter_event): Call linux_low_ptrace_options,
+ use different argument fo linux_enable_event_reporting,
+ use return value from handle_extended_wait.
+ (extended_event_reported): New function.
+ (linux_wait_1): Call extended_event_reported and set
+ status to report fork events.
+ (linux_write_memory): Add pid to debug message.
+ (reset_lwp_ptrace_options_callback): New function.
+ (linux_handle_new_gdb_connection): New function.
+ (linux_target_ops): Initialize new structure member.
+ * linux-low.h (struct lwp_info) <waitstatus>: New member.
+ * lynx-low.c: Initialize new structure member.
+ * remote-utils.c (prepare_resume_reply): Implement stop reason
+ "fork" for "T" stop message.
+ * server.c (handle_query): Call handle_new_gdb_connection.
+ * server.h (report_fork_events): Declare global flag.
+ * target.h (struct target_ops) <handle_new_gdb_connection>:
+ New member.
+ (target_handle_new_gdb_connection): New macro.
+ * win32-low.c: Initialize new structure member.
+
+2015-05-12 Don Breazeal <donb@codesourcery.com>
+
* mem-break.c (APPEND_TO_LIST): Define macro.
(clone_agent_expr): New function.
(clone_one_breakpoint): New function.
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 1724b5491d8..b280c174a97 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -20,6 +20,7 @@
#include "linux-low.h"
#include "nat/linux-osdata.h"
#include "agent.h"
+#include "tdesc.h"
#include "nat/linux-nat.h"
#include "nat/linux-waitpid.h"
@@ -414,22 +415,23 @@ linux_add_process (int pid, int attached)
static CORE_ADDR get_pc (struct lwp_info *lwp);
/* Handle a GNU/Linux extended wait response. If we see a clone
- event, we need to add the new LWP to our list (and not report the
- trap to higher layers). */
+ event, we need to add the new LWP to our list (and return 0 so as
+ not to report the trap to higher layers). */
-static void
-handle_extended_wait (struct lwp_info *event_child, int wstat)
+static int
+handle_extended_wait (struct lwp_info *event_lwp, int wstat)
{
int event = linux_ptrace_get_extended_event (wstat);
- struct thread_info *event_thr = get_lwp_thread (event_child);
+ struct thread_info *event_thr = get_lwp_thread (event_lwp);
struct lwp_info *new_lwp;
- if (event == PTRACE_EVENT_CLONE)
+ if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_CLONE))
{
ptid_t ptid;
unsigned long new_pid;
int ret, status;
+ /* Get the pid of the new lwp. */
ptrace (PTRACE_GETEVENTMSG, lwpid_of (event_thr), (PTRACE_TYPE_ARG3) 0,
&new_pid);
@@ -449,6 +451,57 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
warning ("wait returned unexpected status 0x%x", status);
}
+ if (event == PTRACE_EVENT_FORK)
+ {
+ struct process_info *parent_proc;
+ struct process_info *child_proc;
+ struct lwp_info *child_lwp;
+ struct target_desc *tdesc;
+
+ ptid = ptid_build (new_pid, new_pid, 0);
+
+ if (debug_threads)
+ {
+ debug_printf ("HEW: Got fork event from LWP %ld, "
+ "new child is %d\n",
+ ptid_get_lwp (ptid_of (event_thr)),
+ ptid_get_pid (ptid));
+ }
+
+ /* Add the new process to the tables and clone the breakpoint
+ lists of the parent. We need to do this even if the new process
+ will be detached, since we will need the process object and the
+ breakpoints to remove any breakpoints from memory when we
+ detach, and the client side will access registers. */
+ child_proc = linux_add_process (new_pid, 0);
+ gdb_assert (child_proc != NULL);
+ child_lwp = add_lwp (ptid);
+ gdb_assert (child_lwp != NULL);
+ child_lwp->stopped = 1;
+ parent_proc = get_thread_process (event_thr);
+ child_proc->attached = parent_proc->attached;
+ clone_all_breakpoints (&child_proc->breakpoints,
+ &child_proc->raw_breakpoints,
+ parent_proc->breakpoints);
+
+ tdesc = xmalloc (sizeof (struct target_desc));
+ copy_target_description (tdesc, parent_proc->tdesc);
+ child_proc->tdesc = tdesc;
+ child_lwp->must_set_ptrace_flags = 1;
+
+ /* Save fork info in the parent thread. */
+ event_lwp->waitstatus.kind = TARGET_WAITKIND_FORKED;
+ event_lwp->waitstatus.value.related_pid = ptid;
+ /* The status_pending field contains bits denoting the
+ extended event, so when the pending event is handled,
+ the handler will look at lwp->waitstatus. */
+ event_lwp->status_pending_p = 1;
+ event_lwp->status_pending = wstat;
+
+ /* Report the event. */
+ return 0;
+ }
+
if (debug_threads)
debug_printf ("HEW: Got clone event "
"from LWP %ld, new child is LWP %ld\n",
@@ -477,7 +530,12 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
new_lwp->status_pending_p = 1;
new_lwp->status_pending = status;
}
+
+ /* Don't report the event. */
+ return 1;
}
+
+ internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
}
/* Return the PC as read from the regcache of LWP, without any
@@ -1935,6 +1993,22 @@ check_stopped_by_watchpoint (struct lwp_info *child)
return child->stop_reason == TARGET_STOPPED_BY_WATCHPOINT;
}
+/* Return the ptrace options that we want to try to enable. */
+
+static int
+linux_low_ptrace_options (int attached)
+{
+ int options = 0;
+
+ if (!attached)
+ options |= PTRACE_O_EXITKILL;
+
+ if (report_fork_events)
+ options |= PTRACE_O_TRACEFORK;
+
+ return options;
+}
+
/* Do low-level handling of the event, and check if we should go on
and pass it to caller code. Return the affected lwp if we are, or
NULL otherwise. */
@@ -2022,8 +2096,9 @@ linux_low_filter_event (int lwpid, int wstat)
if (WIFSTOPPED (wstat) && child->must_set_ptrace_flags)
{
struct process_info *proc = find_process_pid (pid_of (thread));
+ int options = linux_low_ptrace_options (proc->attached);
- linux_enable_event_reporting (lwpid, proc->attached);
+ linux_enable_event_reporting (lwpid, options);
child->must_set_ptrace_flags = 0;
}
@@ -2033,8 +2108,12 @@ linux_low_filter_event (int lwpid, int wstat)
&& linux_is_extended_waitstatus (wstat))
{
child->stop_pc = get_pc (child);
- handle_extended_wait (child, wstat);
- return NULL;
+ if (handle_extended_wait (child, wstat))
+ {
+ /* The event has been handled, so just return without
+ reporting it. */
+ return NULL;
+ }
}
/* Check first whether this was a SW/HW breakpoint before checking
@@ -2622,6 +2701,18 @@ ignore_event (struct target_waitstatus *ourstatus)
return null_ptid;
}
+/* Return non-zero if WAITSTATUS reflects an extended linux
+ event. Otherwise, return zero. */
+
+static int
+extended_event_reported (const struct target_waitstatus *waitstatus)
+{
+ if (waitstatus == NULL)
+ return 0;
+
+ return (waitstatus->kind == TARGET_WAITKIND_FORKED);
+}
+
/* Wait for process, returns status. */
static ptid_t
@@ -2988,7 +3079,8 @@ linux_wait_1 (ptid_t ptid,
&& !bp_explains_trap && !trace_event)
|| (gdb_breakpoint_here (event_child->stop_pc)
&& gdb_condition_true_at_breakpoint (event_child->stop_pc)
- && gdb_no_commands_at_breakpoint (event_child->stop_pc)));
+ && gdb_no_commands_at_breakpoint (event_child->stop_pc))
+ || extended_event_reported (&event_child->waitstatus));
run_breakpoint_commands (event_child->stop_pc);
@@ -3010,6 +3102,13 @@ linux_wait_1 (ptid_t ptid,
paddress (event_child->stop_pc),
paddress (event_child->step_range_start),
paddress (event_child->step_range_end));
+ if (extended_event_reported (&event_child->waitstatus))
+ {
+ char *str = target_waitstatus_to_string (ourstatus);
+ debug_printf ("LWP %ld: extended event with waitstatus %s\n",
+ lwpid_of (get_lwp_thread (event_child)), str);
+ xfree (str);
+ }
}
/* We're not reporting this breakpoint to GDB, so apply the
@@ -3119,7 +3218,17 @@ linux_wait_1 (ptid_t ptid,
unstop_all_lwps (1, event_child);
}
- ourstatus->kind = TARGET_WAITKIND_STOPPED;
+ if (extended_event_reported (&event_child->waitstatus))
+ {
+ /* If the reported event is a fork, vfork or exec, let GDB know. */
+ ourstatus->kind = event_child->waitstatus.kind;
+ ourstatus->value = event_child->waitstatus.value;
+
+ /* Clear the event lwp's waitstatus since we handled it already. */
+ event_child->waitstatus.kind = TARGET_WAITKIND_IGNORE;
+ }
+ else
+ ourstatus->kind = TARGET_WAITKIND_STOPPED;
/* Now that we've selected our final event LWP, un-adjust its PC if
it was a software breakpoint, and the client doesn't know we can
@@ -3152,7 +3261,7 @@ linux_wait_1 (ptid_t ptid,
but, it stopped for other reasons. */
ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
}
- else
+ else if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
{
ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
}
@@ -4996,8 +5105,8 @@ linux_write_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int len)
val = val & 0xffff;
else if (len == 3)
val = val & 0xffffff;
- debug_printf ("Writing %0*x to 0x%08lx\n", 2 * ((len < 4) ? len : 4),
- val, (long)memaddr);
+ debug_printf ("Writing %0*x to 0x%08lx in process %d\n",
+ 2 * ((len < 4) ? len : 4), val, (long)memaddr, pid);
}
/* Fill start and end extra bytes of buffer with existing memory data. */
@@ -5454,6 +5563,48 @@ linux_supports_vfork_events (void)
return linux_supports_tracefork ();
}
+/* Callback for 'find_inferior'. Set the (possibly changed) ptrace
+ options for the specified lwp. */
+
+static int
+reset_lwp_ptrace_options_callback (struct inferior_list_entry *entry,
+ void *args)
+{
+ struct thread_info *thread = (struct thread_info *) entry;
+ struct lwp_info *lwp = get_thread_lwp (thread);
+
+ if (!lwp->stopped)
+ {
+ /* Stop the lwp so we can modify its ptrace options. */
+ lwp->must_set_ptrace_flags = 1;
+ linux_stop_lwp (lwp);
+ }
+ else
+ {
+ /* Already stopped; go ahead and set the ptrace options. */
+ struct process_info *proc = find_process_pid (pid_of (thread));
+ int options = linux_low_ptrace_options (proc->attached);
+
+ linux_enable_event_reporting (lwpid_of (thread), options);
+ lwp->must_set_ptrace_flags = 0;
+ }
+
+ return 0;
+}
+
+/* Target hook for 'handle_new_gdb_connection'. Causes a reset of the
+ ptrace flags for all inferiors. This is in case the new GDB connection
+ doesn't support the same set of events that the previous one did. */
+
+static void
+linux_handle_new_gdb_connection (void)
+{
+ pid_t pid;
+
+ /* Request that all the lwps reset their ptrace options. */
+ find_inferior (&all_threads, reset_lwp_ptrace_options_callback , &pid);
+}
+
static int
linux_supports_disable_randomization (void)
{
@@ -6429,6 +6580,7 @@ static struct target_ops linux_target_ops = {
linux_supports_multi_process,
linux_supports_fork_events,
linux_supports_vfork_events,
+ linux_handle_new_gdb_connection,
#ifdef USE_THREAD_DB
thread_db_handle_monitor_command,
#else
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index 1ae37017f57..41067d65ce5 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -271,6 +271,10 @@ struct lwp_info
/* When stopped is set, the last wait status recorded for this lwp. */
int last_status;
+ /* This is used to store extended ptrace event information until
+ it is reported to GDB. */
+ struct target_waitstatus waitstatus;
+
/* When stopped is set, this is where the lwp last stopped, with
decr_pc_after_break already accounted for. If the LWP is
running, this is the address at which the lwp was resumed. */
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index f08741601d1..ee7b28a3a0b 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -764,6 +764,7 @@ static struct target_ops lynx_target_ops = {
NULL, /* supports_multi_process */
NULL, /* supports_fork_events */
NULL, /* supports_vfork_events */
+ NULL, /* handle_new_gdb_connection */
NULL, /* handle_monitor_command */
};
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index 1de86beecfb..60783489b8a 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1114,12 +1114,24 @@ prepare_resume_reply (char *buf, ptid_t ptid,
switch (status->kind)
{
case TARGET_WAITKIND_STOPPED:
+ case TARGET_WAITKIND_FORKED:
{
struct thread_info *saved_thread;
const char **regp;
struct regcache *regcache;
- sprintf (buf, "T%02x", status->value.sig);
+ if (status->kind == TARGET_WAITKIND_FORKED && report_fork_events)
+ {
+ enum gdb_signal signal = GDB_SIGNAL_TRAP;
+
+ sprintf (buf, "T%02xfork:", signal);
+ buf += strlen (buf);
+ buf = write_ptid (buf, status->value.related_pid);
+ strcat (buf, ";");
+ }
+ else
+ sprintf (buf, "T%02x", status->value.sig);
+
buf += strlen (buf);
saved_thread = current_thread;
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 1544e99bdb3..39692672134 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -2160,6 +2160,9 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
if (the_target->pid_to_exec_file != NULL)
strcat (own_buf, ";qXfer:exec-file:read+");
+ /* Reinitialize the target as needed for the new connection. */
+ target_handle_new_gdb_connection ();
+
return;
}
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 91d4080d55e..696a24e8c4a 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -84,6 +84,7 @@ extern int disable_packet_qfThreadInfo;
extern int run_once;
extern int multi_process;
+extern int report_fork_events;
extern int non_stop;
/* True if the "swbreak+" feature is active. In that case, GDB wants
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index 697b223bcb3..8d233834cee 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -287,6 +287,9 @@ struct target_ops
/* Returns true if vfork events are supported. */
int (*supports_vfork_events) (void);
+ /* Allows target to re-initialize connection-specific settings. */
+ void (*handle_new_gdb_connection) (void);
+
/* If not NULL, target-specific routine to process monitor command.
Returns 1 if handled, or 0 to perform default processing. */
int (*handle_monitor_command) (char *);
@@ -434,6 +437,10 @@ int kill_inferior (int);
(the_target->supports_vfork_events ? \
(*the_target->supports_vfork_events) () : 0)
+#define target_handle_new_gdb_connection() \
+ (the_target->handle_new_gdb_connection ? \
+ (*the_target->handle_new_gdb_connection) () : 0)
+
#define detach_inferior(pid) \
(*the_target->detach) (pid)
diff --git a/gdb/gdbserver/win32-low.c b/gdb/gdbserver/win32-low.c
index a58f9634883..699115ca6ba 100644
--- a/gdb/gdbserver/win32-low.c
+++ b/gdb/gdbserver/win32-low.c
@@ -1833,6 +1833,7 @@ static struct target_ops win32_target_ops = {
NULL, /* supports_multi_process */
NULL, /* supports_fork_events */
NULL, /* supports_vfork_events */
+ NULL, /* handle_new_gdb_connection */
NULL, /* handle_monitor_command */
NULL, /* core_of_thread */
NULL, /* read_loadmap */