[PATCH 1/5] Extended-remote exec events

Don Breazeal
2015-07-15T21:50:06Z

This patch is the latest version implementing support for exec
events on extended-remote Linux targets. Follow-exec-mode and
rerun behave as expected. Catchpoints and test updates are
implemented in subsequent patches.

This patch was most recently derived from a patch posted last
October: https://sourceware.org/ml/gdb-patches/2014-10/msg00877.html.
It was originally based on some work done by Luis Machado in 2013.

IMPLEMENTATION
----------------
Support for exec events in single-threaded programs was a fairly
straightforward replication of the implementation in native GDB:

1) Enable exec events via ptrace options.

2) Add support for handling the exec events to the handle_extended_wait and
linux_wait_for_event_filtered. Detect the exec event, then find and save
the pathname of the executable file being exec'd and set event status flags.

3) Implement an additional "stop reason", "exec", in the RSP stop reply
packet "T".

Existing GDB code takes care of handling the exec event on the host side
without modification.

Support for exec events in multi-threaded programs required some additional
work. When exec is called, the Linux kernel destroys all of the threads
except the execing one. If the execing thread was not the thread group
leader, the kernel resets the execing thread's tid to the tgid, and no
exit notification is sent for the execing thread -- from the ptracer's
perspective, it appears as though the execing thread just vanishes.

The non-leader exec leaves gdbserver with one or more of several
potential scenarios which require it to bring its thread lists (e.g.
struct thread_info, struct lwp_info) in sync with reality.

- The leader thread exited before the exec event was reported, and
the execing thread cannot re-use its data structures.

In this case gdbserver must recognize that an exec event occurred
and there are no thread structures for the leader thread, so it
must add new structures to the lists for the 'new' leader thread.

if (WIFSTOPPED (wstat) && (child == NULL) && (WSTOPSIG (wstat) == SIGTRAP)
&& (linux_ptrace_get_extended_event (wstat) == PTRACE_EVENT_EXEC))
{
ptid_t child_ptid;

/* A multi-thread exec after we had seen the leader exiting. */
if (debug_threads)
{
debug_printf ("LLW: Re-adding thread group leader LWP %d"
"after exec.\n", lwpid);
}

child_ptid = ptid_build (lwpid, lwpid, 0);
child = add_lwp (child_ptid);
child->stopped = 1;
current_thread = child->thread;
}

- The execing thread can re-use the previous leader thread's data
structures, and the old data structures used for the execing thread
prior to the exec are left with a running status and no actual
thread associated with it.

When a non-leader execing thread re-uses the previous leader's
thread_info structure, it inherits the old thread's register
cache. If this is left as-is it will eventually be flushed to
the target, clobbering the valid register values with those from
the old thread. So when an EXEC event occurs we always invalidate
the register cache. Note that we can't call regcache_invalidate,
since that flushes the cache to the target.

else if (event == PTRACE_EVENT_EXEC && report_exec_events)
{
struct regcache *regcache;
---snip snip---
regcache = (struct regcache *) inferior_regcache_data (event_thr);
free_register_cache (regcache);
set_inferior_regcache_data (event_thr, NULL);
event_lwp->stop_pc = get_pc (event_lwp);

In this case (stale thread data structures for execing thread) when
in all-stop mode, gdbserver must clean up any stale thread/lwp structures
before it tries to stop all the threads and hangs in sigsuspend, waiting
for an event from a non-existent thread. We do this by checking the
return value from kill_lwp in send_sigstop, eventually called after
calling stop_all_lwps, which is used to stop the threads in all-stop
mode. If kill_lwp returns an error and errno is ESRCH, we know that the
lwp with that pid is gone, and we delete the associated data structures.

- kill_lwp (pid, SIGSTOP);
+ errno = 0;
+ ret = kill_lwp (pid, SIGSTOP);
+ if (ret == -1 && errno == ESRCH)
+ {
+ /* If the kill fails with "No such process", on GNU/Linux we know
+ that the LWP has vanished - it is not a zombie, it is gone.
+ This is because a thread that was not the thread group leader
+ called exec and took over the leader's lwp. */
+ delete_lwp (lwp);
+ set_desired_thread (0);

In the same case in non-stop mode, we don't need to stop all the
lwps, but in order to utilize the same mechanism used in all-stop
mode, we call stop_all_lwps/unstop_all_lwps in succession, just
to check for ESRCH errors and to delete any stale thread structures.

if (non_stop && stopping_threads == NOT_STOPPING_THREADS)
{
/* In non-stop mode, make sure we delete the lwp entry for a
non-leader exec'ing thread, which will have vanished. We
do this by sending a signal to all the other threads in the
lwp list, deleting any that are not found. Note that in
all-stop mode this will happen when we stop all the threads. */
stop_all_lwps (0, event_lwp);
unstop_all_lwps (0, event_lwp);
}

Note that the native implementation uses a different mechanism for
identifying the stale data structure scenario. It determines that the
execing thread has "vanished" by calling waitpid(PID) and checking for a
return value of ECHILD, which means that the thread is gone. We don't want
to use waitpid(PID) in gdbserver, based on the discussion in:

https://www.sourceware.org/ml/gdb-patches/2014-02/msg00828.html

so we use the send_sigstop method described above instead.

TESTING
--------
x86_64 GNU/Linux for native, native-gdbserver, and
native-extended-gdbserver targets. Most of the exec-related tests fail
due to the lack of catchpoints and extended-remote support in the tests.

Thanks
--Don

gdb/gdbserver/
2015-07-15 Don Breazeal <donb@codesourcery.com>
Luis Machado <lgustavo@codesourcery.com>

* linux-low.c (handle_extended_wait): Handle exec events.
(check_zombie_leaders): Do not check stopped threads.
(linux_low_ptrace_options): Add PTRACE_O_TRACEEXEC.
(linux_low_filter_event): Add thread structures for exec'ing
non-leader thread after leader thread had been deleted.
(linux_wait_for_event_filtered): Fix comment saying exec events
are not supported in remote.
(extended_event_reported): Add TARGET_WAITKIND_EXECD.
(linux_wait_1): Prevent clobbering extended event status.
(send_sigstop): Check return from kill_lwp, and if ESRCH then
call delete_lwp.
(linux_supports_exec_events): New function.
* lynx-low.c (lynx_target_ops): Initialize new structure
member 'supports_exec_events'.
* remote-utils.c (prepare_resume_reply): New stop reason 'exec'.
* server.c (report_exec_events): New global variable.
(handle_query): Handle qSupported query for exec-events feature.
(captured_main): Initialize report_exec_events.
* server.h (report_exec_events): Declare new global variable.
* target.h (struct target_ops) <supports_exec_events>: New
member.
(target_supports_exec_events): New macro.
* win32-low.c (win32_target_ops): Initialize new structure
member 'supports_exec_events'.

gdb/
2015-07-15 Don Breazeal <donb@codesourcery.com>
Luis Machado <lgustavo@codesourcery.com>

* nat/linux-ptrace.c (linux_supports_tracefork): Delete
out-of-date comment verbiage.
(linux_supports_traceexec): New function.
* nat/linux-ptrace.h (linux_supports_traceexec): Declare.
* remote.c (anonymous enum) <PACKET_exec_event_feature> New
enumeration constant.
(remote_protocol_features): Add entry for exec-events feature.
(remote_query_supported): Add client side of qSupported query
for exec-events feature.
(_initialize_remote): Call add_packet_config_cmd for remote
exec-events feature.

---
gdb/gdbserver/linux-low.c | 117 ++++++++++++++++++++++++++++++++++++++++---
gdb/gdbserver/lynx-low.c | 1 +
gdb/gdbserver/remote-utils.c | 20 ++++++++
gdb/gdbserver/server.c | 11 ++++
gdb/gdbserver/server.h | 1 +
gdb/gdbserver/target.h | 7 +++
gdb/gdbserver/win32-low.c | 1 +
gdb/nat/linux-ptrace.c | 13 +++--
gdb/nat/linux-ptrace.h | 1 +
gdb/remote.c | 30 +++++++++++
10 files changed, 193 insertions(+), 9 deletions(-)

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 2dafb03..47d8bc3 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -557,6 +557,52 @@ handle_extended_wait (struct lwp_info *event_lwp, int wstat)
/* Report the event. */
return 0;
}
+ else if (event == PTRACE_EVENT_EXEC && report_exec_events)
+ {
+ struct regcache *regcache;
+
+ if (debug_threads)
+ {
+ debug_printf ("HEW: Got exec event from LWP %ld\n",
+ lwpid_of (event_thr));
+ }
+
+ /* If the exec was not called by the thread group leader, then
+ the lwp_info and thread_info structures are out-of-date,
+ containing information about the original leader thread and
+ not the new exec'ing leader thread. Invalidate the register
+ cache without flushing it to the target, and reset the stop
+ pc value in the lwp. */
+ regcache = (struct regcache *) inferior_regcache_data (event_thr);
+ free_register_cache (regcache);
+ set_inferior_regcache_data (event_thr, NULL);
+ event_lwp->stop_pc = get_pc (event_lwp);
+
+ event_lwp->waitstatus.kind = TARGET_WAITKIND_EXECD;
+ event_lwp->waitstatus.value.execd_pathname
+ = xstrdup (linux_proc_pid_to_exec_file (lwpid_of (event_thr)));
+
+ /* Mark the exec status as pending. */
+ event_lwp->stopped = 1;
+ event_lwp->status_pending_p = 1;
+ event_lwp->status_pending = wstat;
+ event_thr->last_resume_kind = resume_stop;
+ event_thr->last_status.kind = TARGET_WAITKIND_IGNORE;
+
+ if (non_stop && stopping_threads == NOT_STOPPING_THREADS)
+ {
+ /* In non-stop mode, make sure we delete the lwp entry for a
+ non-leader exec'ing thread, which will have vanished. We
+ do this by sending a signal to all the other threads in the
+ lwp list, deleting any that are not found. Note that in
+ all-stop mode this will happen when we stop all the threads. */
+ stop_all_lwps (0, event_lwp);
+ unstop_all_lwps (0, event_lwp);
+ }
+
+ /* Report the event. */
+ return 0;
+ }

internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
}
@@ -1609,7 +1655,7 @@ check_zombie_leaders (void)
leader_pid, leader_lp!= NULL, num_lwps (leader_pid),
linux_proc_pid_is_zombie (leader_pid));

- if (leader_lp != NULL
+ if (leader_lp != NULL && !leader_lp->stopped
/* Check if there are other threads in the group, as we may
have raced with the inferior simply exiting. */
&& !last_thread_of_process_p (leader_pid)
@@ -2035,6 +2081,9 @@ linux_low_ptrace_options (int attached)
if (report_vfork_events)
options |= (PTRACE_O_TRACEVFORK | PTRACE_O_TRACEVFORKDONE);

+ if (report_exec_events)
+ options |= PTRACE_O_TRACEEXEC;
+
return options;
}

@@ -2051,6 +2100,38 @@ linux_low_filter_event (int lwpid, int wstat)

child = find_lwp_pid (pid_to_ptid (lwpid));

+ /* Check for stop events reported by a process we didn't already
+ know about - anything not already in our LWP list.
+
+ If we're expecting to receive stopped processes after
+ fork, vfork, and clone events, then we'll just add the
+ new one to our list and go back to waiting for the event
+ to be reported - the stopped process might be returned
+ from waitpid before or after the event is.
+
+ But note the case of a non-leader thread exec'ing after the
+ leader having exited, and gone from our lists (because
+ check_zombie_leaders deleted it). The non-leader thread
+ changes its tid to the tgid. */
+
+ if (WIFSTOPPED (wstat) && (child == NULL) && (WSTOPSIG (wstat) == SIGTRAP)
+ && (linux_ptrace_get_extended_event (wstat) == PTRACE_EVENT_EXEC))
+ {
+ ptid_t child_ptid;
+
+ /* A multi-thread exec after we had seen the leader exiting. */
+ if (debug_threads)
+ {
+ debug_printf ("LLW: Re-adding thread group leader LWP %d"
+ "after exec.\n", lwpid);
+ }
+
+ child_ptid = ptid_build (lwpid, lwpid, 0);
+ child = add_lwp (child_ptid);
+ child->stopped = 1;
+ current_thread = child->thread;
+ }
+
/* If we didn't find a process, one of two things presumably happened:
- A process we started and then detached from has exited. Ignore it.
- A process we are controlling has forked and the new child's stop
@@ -2342,8 +2423,7 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
- When a non-leader thread execs, that thread just vanishes
without reporting an exit (so we'd hang if we waited for it
explicitly in that case). The exec event is reported to
- the TGID pid (although we don't currently enable exec
- events). */
+ the TGID pid. */
errno = 0;
ret = my_waitpid (-1, wstatp, options | WNOHANG);

@@ -2741,7 +2821,8 @@ extended_event_reported (const struct target_waitstatus *waitstatus)

return (waitstatus->kind == TARGET_WAITKIND_FORKED
|| waitstatus->kind == TARGET_WAITKIND_VFORKED
- || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE);
+ || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE
+ || waitstatus->kind == TARGET_WAITKIND_EXECD);
}

/* Wait for process, returns status. */
@@ -3286,7 +3367,8 @@ linux_wait_1 (ptid_t ptid,
ourstatus->value.sig = GDB_SIGNAL_0;
}
else if (current_thread->last_resume_kind == resume_stop
- && WSTOPSIG (w) != SIGSTOP)
+ && WSTOPSIG (w) != SIGSTOP
+ && !extended_event_reported (ourstatus))
{
/* A thread that has been requested to stop by GDB with vCont;t,
but, it stopped for other reasons. */
@@ -3404,6 +3486,7 @@ static void
send_sigstop (struct lwp_info *lwp)
{
int pid;
+ int ret;

pid = lwpid_of (get_lwp_thread (lwp));

@@ -3421,7 +3504,20 @@ send_sigstop (struct lwp_info *lwp)
debug_printf ("Sending sigstop to lwp %d\n", pid);

lwp->stop_expected = 1;
- kill_lwp (pid, SIGSTOP);
+ errno = 0;
+ ret = kill_lwp (pid, SIGSTOP);
+ if (ret == -1 && errno == ESRCH)
+ {
+ /* If the kill fails with "No such process", on GNU/Linux we know
+ that the LWP has vanished - it is not a zombie, it is gone.
+ This is because a thread that was not the thread group leader
+ called exec and took over the leader's lwp. */
+ delete_lwp (lwp);
+ set_desired_thread (0);
+
+ if (debug_threads)
+ debug_printf ("send_sigstop: lwp %d has vanished\n", pid);
+ }
}

static int
@@ -5594,6 +5690,14 @@ linux_supports_vfork_events (void)
return linux_supports_tracefork ();
}

+/* Check if exec events are supported. */
+
+static int
+linux_supports_exec_events (void)
+{
+ return linux_supports_traceexec ();
+}
+
/* Callback for 'find_inferior'. Set the (possibly changed) ptrace
options for the specified lwp. */

@@ -6681,6 +6785,7 @@ static struct target_ops linux_target_ops = {
linux_supports_multi_process,
linux_supports_fork_events,
linux_supports_vfork_events,
+ linux_supports_exec_events,
linux_handle_new_gdb_connection,
#ifdef USE_THREAD_DB
thread_db_handle_monitor_command,
diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index ee7b28a..fd89869 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, /* supports_exec_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 bb31456..79d5ee8 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1116,6 +1116,7 @@ prepare_resume_reply (char *buf, ptid_t ptid,
case TARGET_WAITKIND_STOPPED:
case TARGET_WAITKIND_FORKED:
case TARGET_WAITKIND_VFORKED:
+ case TARGET_WAITKIND_EXECD:
{
struct thread_info *saved_thread;
const char **regp;
@@ -1133,6 +1134,25 @@ prepare_resume_reply (char *buf, ptid_t ptid,
buf = write_ptid (buf, status->value.related_pid);
strcat (buf, ";");
}
+ else if ((status->kind == TARGET_WAITKIND_EXECD) && multi_process)
+ {
+ enum gdb_signal signal = GDB_SIGNAL_TRAP;
+ const char *event = "exec";
+ char hexified_pathname[PATH_MAX];
+
+ sprintf (buf, "T%02x%s:", signal, event);
+ buf += strlen (buf);
+
+ /* Encode pathname to hexified format. */
+ bin2hex ((const gdb_byte *) status->value.execd_pathname,
+ hexified_pathname,
+ strlen (status->value.execd_pathname));
+
+ sprintf (buf, "%s;", hexified_pathname);
+ xfree (status->value.execd_pathname);
+ status->value.execd_pathname = NULL;
+ buf += strlen (buf);
+ }
else
sprintf (buf, "T%02x", status->value.sig);

diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 7e388dd..2b98d7c 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -59,6 +59,7 @@ int run_once;
int multi_process;
int report_fork_events;
int report_vfork_events;
+int report_exec_events;
int non_stop;
int swbreak_feature;
int hwbreak_feature;
@@ -2107,6 +2108,12 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
if (target_supports_vfork_events ())
report_vfork_events = 1;
}
+ if (strcmp (p, "exec-events+") == 0)
+ {
+ /* GDB supports and wants exec events if possible. */
+ if (target_supports_exec_events ())
+ report_exec_events = 1;
+ }
else
target_process_qsupported (p);

@@ -2163,6 +2170,9 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
if (target_supports_vfork_events ())
strcat (own_buf, ";vfork-events+");

+ if (target_supports_exec_events ())
+ strcat (own_buf, ";exec-events+");
+
if (target_supports_non_stop ())
strcat (own_buf, ";QNonStop+");

@@ -3545,6 +3555,7 @@ captured_main (int argc, char *argv[])
multi_process = 0;
report_fork_events = 0;
report_vfork_events = 0;
+ report_exec_events = 0;
/* Be sure we're out of tfind mode. */
current_traceframe = -1;
cont_thread = null_ptid;
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 09a5624..258909d 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -86,6 +86,7 @@ extern int run_once;
extern int multi_process;
extern int report_fork_events;
extern int report_vfork_events;
+extern int report_exec_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 9a40867..dd5eb3e 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);

+ /* Returns true if exec events are supported. */
+ int (*supports_exec_events) (void);
+
/* Allows target to re-initialize connection-specific settings. */
void (*handle_new_gdb_connection) (void);

@@ -458,6 +461,10 @@ int kill_inferior (int);
(the_target->supports_vfork_events ? \
(*the_target->supports_vfork_events) () : 0)

+#define target_supports_exec_events() \
+ (the_target->supports_exec_events ? \
+ (*the_target->supports_exec_events) () : 0)
+
#define target_handle_new_gdb_connection() \
do \
{ \
diff --git a/gdb/gdbserver/win32-low.c b/gdb/gdbserver/win32-low.c
index 64caf24..f7a3c0b 100644
--- a/gdb/gdbserver/win32-low.c
+++ b/gdb/gdbserver/win32-low.c
@@ -1831,6 +1831,7 @@ static struct target_ops win32_target_ops = {
NULL, /* supports_multi_process */
NULL, /* supports_fork_events */
NULL, /* supports_vfork_events */
+ NULL, /* supports_exec_events */
NULL, /* handle_new_gdb_connection */
NULL, /* handle_monitor_command */
NULL, /* core_of_thread */
diff --git a/gdb/nat/linux-ptrace.c b/gdb/nat/linux-ptrace.c
index 1a926f9..db3c4ce 100644
--- a/gdb/nat/linux-ptrace.c
+++ b/gdb/nat/linux-ptrace.c
@@ -527,9 +527,7 @@ ptrace_supports_feature (int ptrace_options)
}

/* Returns non-zero if PTRACE_EVENT_FORK is supported by ptrace,
- 0 otherwise. Note that if PTRACE_EVENT_FORK is supported so is
- PTRACE_EVENT_CLONE, PTRACE_EVENT_EXEC and PTRACE_EVENT_VFORK,
- since they were all added to the kernel at the same time. */
+ 0 otherwise. */

int
linux_supports_tracefork (void)
@@ -537,6 +535,15 @@ linux_supports_tracefork (void)
return ptrace_supports_feature (PTRACE_O_TRACEFORK);
}

+/* Returns non-zero if PTRACE_EVENT_EXEC is supported by ptrace,
+ 0 otherwise. */
+
+int
+linux_supports_traceexec (void)
+{
+ return ptrace_supports_feature (PTRACE_O_TRACEEXEC);
+}
+
/* Returns non-zero if PTRACE_EVENT_CLONE is supported by ptrace,
0 otherwise. Note that if PTRACE_EVENT_CLONE is supported so is
PTRACE_EVENT_FORK, PTRACE_EVENT_EXEC and PTRACE_EVENT_VFORK,
diff --git a/gdb/nat/linux-ptrace.h b/gdb/nat/linux-ptrace.h
index be6c395..6d534ee 100644
--- a/gdb/nat/linux-ptrace.h
+++ b/gdb/nat/linux-ptrace.h
@@ -161,6 +161,7 @@ extern void linux_check_ptrace_features (void);
extern void linux_enable_event_reporting (pid_t pid, int attached);
extern void linux_disable_event_reporting (pid_t pid);
extern int linux_supports_tracefork (void);
+extern int linux_supports_traceexec (void);
extern int linux_supports_traceclone (void);
extern int linux_supports_tracevforkdone (void);
extern int linux_supports_tracesysgood (void);
diff --git a/gdb/remote.c b/gdb/remote.c
index 9d97f6b..c0b3423 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -1366,6 +1366,9 @@ enum {
/* Support for the Qbtrace-conf:pt:size packet. */
PACKET_Qbtrace_conf_pt_size,

+ /* Support for exec events. */
+ PACKET_exec_event_feature,
+
PACKET_MAX
};

@@ -4162,6 +4165,8 @@ static const struct protocol_feature remote_protocol_features[] = {
PACKET_fork_event_feature },
{ "vfork-events", PACKET_DISABLE, remote_supported_packet,
PACKET_vfork_event_feature },
+ { "exec-events", PACKET_DISABLE, remote_supported_packet,
+ PACKET_exec_event_feature },
{ "Qbtrace-conf:pt:size", PACKET_DISABLE, remote_supported_packet,
PACKET_Qbtrace_conf_pt_size }
};
@@ -4250,6 +4255,9 @@ remote_query_supported (void)
if (packet_set_cmd_state (PACKET_vfork_event_feature)
!= AUTO_BOOLEAN_FALSE)
q = remote_query_supported_append (q, "vfork-events+");
+ if (packet_set_cmd_state (PACKET_exec_event_feature)
+ != AUTO_BOOLEAN_FALSE)
+ q = remote_query_supported_append (q, "exec-events+");
}

q = reconcat (q, "qSupported:", q, (char *) NULL);
@@ -5909,6 +5917,25 @@ Packet: '%s'\n"),
event->ws.kind = TARGET_WAITKIND_VFORK_DONE;
p = skip_to_semicolon (p1 + 1);
}
+ else if (strncmp (p, "exec", p1 - p) == 0)
+ {
+ ULONGEST pid;
+ char pathname[PATH_MAX];
+
+ p = unpack_varlen_hex (++p1, &pid);
+
+ /* Save the pathname for event reporting and for
+ the next run command. */
+ hex2bin (p1, (gdb_byte *) pathname, (p - p1)/2);
+ /* Add the null terminator. */
+ pathname[(p - p1)/2] = '\0';
+ /* This is freed during event handling. */
+ event->ws.value.execd_pathname = xstrdup (pathname);
+ event->ws.kind = TARGET_WAITKIND_EXECD;
+ /* Save the pathname for the next run command. */
+ xfree (remote_exec_file);
+ remote_exec_file = xstrdup (pathname);
+ }
else
{
ULONGEST pnum;
@@ -12960,6 +12987,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
add_packet_config_cmd (&remote_protocol_packets[PACKET_Qbtrace_conf_pt_size],
"Qbtrace-conf:pt:size", "btrace-conf-pt-size", 0);

+ add_packet_config_cmd (&remote_protocol_packets[PACKET_exec_event_feature],
+ "exec-event-feature", "exec-event-feature", 0);
+
/* Assert that we've registered "set remote foo-packet" commands
for all packet configs. */
{
--
1.8.1.1