This is the mail archive of the
gdb-patches@sourceware.org
mailing list for the GDB project.
[PATCH v4] Implement 'catch syscall' for gdbserver
- From: Josh Stone <jistone at redhat dot com>
- To: gdb-patches at sourceware dot org
- Cc: philippe dot waroquiers at skynet dot be, sergiodj at redhat dot com, palves at redhat dot com, eliz at gnu dot org, xdje42 at gmail dot com, scox at redhat dot com, Josh Stone <jistone at redhat dot com>
- Date: Fri, 8 Jan 2016 19:09:14 -0800
- Subject: [PATCH v4] Implement 'catch syscall' for gdbserver
- Authentication-results: sourceware.org; auth=none
- References: <1449196006-13759-2-git-send-email-jistone at redhat dot com>
This adds a new QCatchSyscalls packet to enable 'catch syscall', and new
stop reasons "syscall_entry" and "syscall_return" for those events. It
is currently only supported on Linux x86 and x86_64.
gdb/ChangeLog:
2016-01-08 Josh Stone <jistone@redhat.com>
Philippe Waroquiers <philippe.waroquiers@skynet.be>
* NEWS (Changes since GDB 7.10): Mention QCatchSyscalls and the
syscall_entry and syscall_return stop reasons. Mention GDB
support for remote catch syscall.
* remote.c (PACKET_QCatchSyscalls): New enum.
(remote_set_syscall_catchpoint): New function.
(remote_protocol_features): New element for QCatchSyscalls.
(remote_parse_stop_reply): Parse syscall_entry/return stops.
(init_remote_ops): Install remote_set_syscall_catchpoint.
(_initialize_remote): Config QCatchSyscalls.
* linux-nat.h (struct lwp_info) <syscall_state>: Comment typo.
gdb/doc/ChangeLog:
2016-01-08 Josh Stone <jistone@redhat.com>
Philippe Waroquiers <philippe.waroquiers@skynet.be>
* gdb.texinfo (Remote Configuration): List the QCatchSyscalls packet.
(Stop Reply Packets): List the syscall entry and return stop reasons.
(General Query Packets): Describe QCatchSyscalls, and add it to the
table and detailed list of stub features.
gdb/gdbserver/ChangeLog:
2016-01-08 Josh Stone <jistone@redhat.com>
Philippe Waroquiers <philippe.waroquiers@skynet.be>
* inferiors.h: Include "gdb_vecs.h".
(struct process_info): Add syscalls_to_catch.
* inferiors.c (remove_process): Free syscalls_to_catch.
* remote-utils.c (prepare_resume_reply): Report syscall_entry and
syscall_return stops.
* server.h (UNKNOWN_SYSCALL, ANY_SYSCALL): Define.
* server.c (handle_general_set): Handle QCatchSyscalls.
(handle_query): Report support for QCatchSyscalls.
* target.h (struct target_ops): Add supports_catch_syscall.
(target_supports_catch_syscall): New macro.
* linux-low.h (struct linux_target_ops): Add get_syscall_trapinfo.
(struct lwp_info): Add syscall_state.
* linux-low.c (handle_extended_wait): Mark syscall_state as an entry.
Maintain syscall_state and syscalls_to_catch across exec.
(get_syscall_trapinfo): New function, proxy to the_low_target.
(linux_low_ptrace_options): Enable PTRACE_O_TRACESYSGOOD.
(linux_low_filter_event): Toggle syscall_state entry/return for
syscall traps, and set it ignored for all others.
(gdb_catching_syscalls_p): New function.
(gdb_catch_this_syscall_p): New function.
(linux_wait_1): Handle SYSCALL_SIGTRAP.
(linux_resume_one_lwp_throw): Add PTRACE_SYSCALL possibility.
(linux_supports_catch_syscall): New function.
(linux_target_ops): Install it.
* linux-x86-low.c (x86_get_syscall_trapinfo): New function.
(the_low_target): Install it.
gdb/testsuite/ChangeLog:
2016-01-08 Josh Stone <jistone@redhat.com>
Philippe Waroquiers <philippe.waroquiers@skynet.be>
* gdb.base/catch-syscall.c (do_execve): New variable.
(main): Conditionally trigger an execve.
* gdb.base/catch-syscall.exp: Enable testing for remote targets.
(test_catch_syscall_execve): New, check entry/return across execve.
(do_syscall_tests): Call test_catch_syscall_execve.
---
gdb/NEWS | 24 +++++
gdb/doc/gdb.texinfo | 61 ++++++++++++
gdb/gdbserver/inferiors.c | 1 +
gdb/gdbserver/inferiors.h | 6 ++
gdb/gdbserver/linux-low.c | 155 ++++++++++++++++++++++++++++++-
gdb/gdbserver/linux-low.h | 13 +++
gdb/gdbserver/linux-x86-low.c | 26 ++++++
gdb/gdbserver/remote-utils.c | 12 +++
gdb/gdbserver/server.c | 51 ++++++++++
gdb/gdbserver/server.h | 6 ++
gdb/gdbserver/target.h | 8 ++
gdb/linux-nat.h | 2 +-
gdb/remote.c | 110 ++++++++++++++++++++++
gdb/testsuite/gdb.base/catch-syscall.c | 9 +-
gdb/testsuite/gdb.base/catch-syscall.exp | 31 ++++++-
15 files changed, 507 insertions(+), 8 deletions(-)
diff --git a/gdb/NEWS b/gdb/NEWS
index 484d98d24143..adb4d9790065 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -120,6 +120,21 @@ N stop reply
threads are stopped). The remote stub reports support for this stop
reply to GDB's qSupported query.
+QCatchSyscalls:1 [;SYSNO]...
+QCatchSyscalls:0
+ Enable ("QCatchSyscalls:1") or disable ("QCatchSyscalls:0")
+ catching syscalls from the inferior process.
+
+syscall_entry stop reason
+ Indicates that a syscall was just called.
+
+syscall_return stop reason
+ Indicates that a syscall just returned.
+
+QCatchSyscalls:1 in qSupported
+ The qSupported packet may now include QCatchSyscalls:1 in the reply
+ to indicate support for catching syscalls.
+
* Extended-remote exec events
** GDB now has support for exec events on extended-remote Linux targets.
@@ -142,6 +157,15 @@ show remote exec-event-feature-packet
this enables follow-fork-mode, detach-on-fork, follow-exec-mode, and
fork and exec catchpoints.
+* Remote syscall events
+
+ ** GDB now has support for catch syscall on remote Linux targets,
+ currently enabled on x86/x86_64 architectures.
+
+set remote catch-syscall-packet
+show remote catch-syscall-packet
+ Set/show the use of the remote catch syscall feature.
+
* MI changes
** The -var-set-format command now accepts the zero-hexadecimal
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 0778383280a7..e18b27ec4315 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -20261,6 +20261,10 @@ are:
@tab @code{qSupported}
@tab Remote communications parameters
+@item @code{catch-syscalls}
+@tab @code{QCatchSyscalls}
+@tab @code{catch syscall}
+
@item @code{pass-signals}
@tab @code{QPassSignals}
@tab @code{handle @var{signal}}
@@ -35580,6 +35584,11 @@ The currently defined stop reasons are:
The packet indicates a watchpoint hit, and @var{r} is the data address, in
hex.
+@item syscall_entry
+@itemx syscall_return
+The packet indicates a syscall entry or return, and @var{r} is the
+syscall number, in hex.
+
@cindex shared library events, remote reply
@item library
The packet indicates that the loaded libraries have changed.
@@ -36072,6 +36081,49 @@ by supplying an appropriate @samp{qSupported} response (@pxref{qSupported}).
Use of this packet is controlled by the @code{set non-stop} command;
@pxref{Non-Stop Mode}.
+@item QCatchSyscalls:1 @r{[};@var{sysno}@r{]}@dots{}
+@itemx QCatchSyscalls:0
+@cindex catch syscalls from inferior, remote request
+@cindex @samp{QCatchSyscalls} packet
+@anchor{QCatchSyscalls}
+Enable (@samp{QCatchSyscalls:1}) or disable (@samp{QCatchSyscalls:0})
+catching syscalls from the inferior process.
+
+For @samp{QCatchSyscalls:1}, each listed syscall @var{sysno} (encoded
+in hex) should be reported to @value{GDBN}. If no syscall @var{sysno}
+is listed, every system call should be reported.
+
+Note that if a syscall not in the list is reported, @value{GDBN} will
+still filter the event according to its own list from all corresponding
+@code{catch syscall} commands. However, it is more efficient to only
+report the requested syscalls.
+
+Multiple @samp{QCatchSyscalls:1} packets do not combine; any earlier
+@samp{QCatchSyscalls:1} list is completely replaced by the new list.
+
+If the inferior process execs, the state of @samp{QCatchSyscalls} is
+kept for the new process too. On targets where exec may affect syscall
+numbers, for example with exec between 32 and 64-bit processes, the
+client should send a new packet with the new syscall list.
+
+Reply:
+@table @samp
+@item OK
+The request succeeded.
+
+@item E @var{nn}
+An error occurred. @var{nn} are hex digits.
+
+@item @w{}
+An empty reply indicates that @samp{QCatchSyscalls} is not supported by
+the stub.
+@end table
+
+Use of this packet is controlled by the @code{set remote catch-syscalls}
+command (@pxref{Remote Configuration, set remote catch-syscalls}).
+This packet is not probed by default; the remote stub must request it,
+by supplying an appropriate @samp{qSupported} response (@pxref{qSupported}).
+
@item QPassSignals: @var{signal} @r{[};@var{signal}@r{]}@dots{}
@cindex pass signals to inferior, remote request
@cindex @samp{QPassSignals} packet
@@ -36523,6 +36575,11 @@ These are the currently defined stub features and their properties:
@tab @samp{-}
@tab Yes
+@item @samp{QCatchSyscalls}
+@tab No
+@tab @samp{-}
+@tab Yes
+
@item @samp{QPassSignals}
@tab No
@tab @samp{-}
@@ -36726,6 +36783,10 @@ packet (@pxref{qXfer fdpic loadmap read}).
The remote stub understands the @samp{QNonStop} packet
(@pxref{QNonStop}).
+@item QCatchSyscalls
+The remote stub understands the @samp{QCatchSyscalls} packet
+(@pxref{QCatchSyscalls}).
+
@item QPassSignals
The remote stub understands the @samp{QPassSignals} packet
(@pxref{QPassSignals}).
diff --git a/gdb/gdbserver/inferiors.c b/gdb/gdbserver/inferiors.c
index c884b55f383a..4bea4fd7a91b 100644
--- a/gdb/gdbserver/inferiors.c
+++ b/gdb/gdbserver/inferiors.c
@@ -339,6 +339,7 @@ remove_process (struct process_info *process)
free_all_breakpoints (process);
gdb_assert (find_thread_process (process) == NULL);
remove_inferior (&all_processes, &process->entry);
+ VEC_free (int, process->syscalls_to_catch);
free (process);
}
diff --git a/gdb/gdbserver/inferiors.h b/gdb/gdbserver/inferiors.h
index af65718ad4f9..00dfe60e0c5f 100644
--- a/gdb/gdbserver/inferiors.h
+++ b/gdb/gdbserver/inferiors.h
@@ -19,6 +19,8 @@
#ifndef INFERIORS_H
#define INFERIORS_H
+#include "gdb_vecs.h"
+
/* Generic information for tracking a list of ``inferiors'' - threads,
processes, etc. */
struct inferior_list
@@ -67,6 +69,10 @@ struct process_info
/* The list of installed fast tracepoints. */
struct fast_tracepoint_jump *fast_tracepoint_jumps;
+ /* The list of syscalls to report, or just a single element, ANY_SYSCALL,
+ for unfiltered syscall reporting. */
+ VEC (int) *syscalls_to_catch;
+
const struct target_desc *tdesc;
/* Private target data. */
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 4f8f57392101..c787309b53a0 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -461,6 +461,11 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
gdb_assert (event_lwp->waitstatus.kind == TARGET_WAITKIND_IGNORE);
+ /* All extended events we currently use are mid-syscall. Only
+ PTRACE_EVENT_STOP is delivered more like a signal-stop, but
+ you have to be using PTRACE_SEIZE to get that. */
+ event_lwp->syscall_state = TARGET_WAITKIND_SYSCALL_ENTRY;
+
if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
|| (event == PTRACE_EVENT_CLONE))
{
@@ -611,6 +616,7 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
else if (event == PTRACE_EVENT_EXEC && report_exec_events)
{
struct process_info *proc;
+ VEC (int) *syscalls_to_catch;
ptid_t event_ptid;
pid_t event_pid;
@@ -624,8 +630,12 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
event_ptid = ptid_of (event_thr);
event_pid = ptid_get_pid (event_ptid);
- /* Delete the execing process and all its threads. */
+ /* Save the syscall list from the execing process. */
proc = get_thread_process (event_thr);
+ syscalls_to_catch = proc->syscalls_to_catch;
+ proc->syscalls_to_catch = NULL;
+
+ /* Delete the execing process and all its threads. */
linux_mourn (proc);
current_thread = NULL;
@@ -648,6 +658,12 @@ handle_extended_wait (struct lwp_info **orig_event_lwp, int wstat)
event_thr->last_resume_kind = resume_continue;
event_thr->last_status.kind = TARGET_WAITKIND_IGNORE;
+ /* Update syscall state in the new lwp, effectively mid-syscall too.
+ The client really should send a new list to catch, in case the
+ architecture changed, but for ANY_SYSCALL it doesn't matter. */
+ event_lwp->syscall_state = TARGET_WAITKIND_SYSCALL_ENTRY;
+ proc->syscalls_to_catch = syscalls_to_catch;
+
/* Report the event. */
*orig_event_lwp = event_lwp;
return 0;
@@ -682,6 +698,40 @@ get_pc (struct lwp_info *lwp)
return pc;
}
+/* This function should only be called if LWP got a SYSCALL_SIGTRAP.
+ Fill *SYSNO with the syscall nr trapped. Fill *SYSRET with the
+ return code. */
+
+static void
+get_syscall_trapinfo (struct lwp_info *lwp, int *sysno, int *sysret)
+{
+ struct thread_info *saved_thread;
+ struct regcache *regcache;
+
+ if (the_low_target.get_syscall_trapinfo == NULL)
+ {
+ /* If we cannot get the syscall trapinfo, report an unknown
+ system call number and -ENOSYS return value. */
+ *sysno = UNKNOWN_SYSCALL;
+ *sysret = -ENOSYS;
+ return;
+ }
+
+ saved_thread = current_thread;
+ current_thread = get_lwp_thread (lwp);
+
+ regcache = get_thread_regcache (current_thread, 1);
+ (*the_low_target.get_syscall_trapinfo) (regcache, sysno, sysret);
+
+ if (debug_threads)
+ {
+ debug_printf ("get_syscall_trapinfo sysno %d sysret %d\n",
+ *sysno, *sysret);
+ }
+
+ current_thread = saved_thread;
+}
+
/* This function should only be called if LWP got a SIGTRAP.
The SIGTRAP could mean several things.
@@ -2236,6 +2286,8 @@ linux_low_ptrace_options (int attached)
if (report_exec_events)
options |= PTRACE_O_TRACEEXEC;
+ options |= PTRACE_O_TRACESYSGOOD;
+
return options;
}
@@ -2364,6 +2416,21 @@ linux_low_filter_event (int lwpid, int wstat)
child->must_set_ptrace_flags = 0;
}
+ /* Always update syscall_state, even if it will be filtered later. */
+ if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SYSCALL_SIGTRAP)
+ {
+ child->syscall_state
+ = (child->syscall_state == TARGET_WAITKIND_SYSCALL_ENTRY
+ ? TARGET_WAITKIND_SYSCALL_RETURN
+ : TARGET_WAITKIND_SYSCALL_ENTRY);
+ }
+ else
+ {
+ /* Almost all other ptrace-stops are known to be outside of system
+ calls, with further exceptions in handle_extended_wait. */
+ child->syscall_state = TARGET_WAITKIND_IGNORE;
+ }
+
/* Be careful to not overwrite stop_pc until
check_stopped_by_breakpoint is called. */
if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
@@ -2973,6 +3040,44 @@ filter_exit_event (struct lwp_info *event_child,
return ptid;
}
+/* Returns 1 if GDB is interested in any event_child syscalls. */
+
+static int
+gdb_catching_syscalls_p (struct lwp_info *event_child)
+{
+ struct thread_info *thread = get_lwp_thread (event_child);
+ struct process_info *proc = get_thread_process (thread);
+
+ return !VEC_empty (int, proc->syscalls_to_catch);
+}
+
+/* Returns 1 if GDB is interested in the event_child syscall.
+ Only to be called when stopped reason is SYSCALL_SIGTRAP. */
+
+static int
+gdb_catch_this_syscall_p (struct lwp_info *event_child)
+{
+ int i, iter;
+ int sysno, sysret;
+ struct thread_info *thread = get_lwp_thread (event_child);
+ struct process_info *proc = get_thread_process (thread);
+
+ if (VEC_empty (int, proc->syscalls_to_catch))
+ return 0;
+
+ if (VEC_index (int, proc->syscalls_to_catch, 0) == ANY_SYSCALL)
+ return 1;
+
+ get_syscall_trapinfo (event_child, &sysno, &sysret);
+ for (i = 0;
+ VEC_iterate (int, proc->syscalls_to_catch, i, iter);
+ i++)
+ if (iter == sysno)
+ return 1;
+
+ return 0;
+}
+
/* Wait for process, returns status. */
static ptid_t
@@ -3307,6 +3412,22 @@ linux_wait_1 (ptid_t ptid,
/* Check whether GDB would be interested in this event. */
+ /* Check if GDB is interested in this syscall. */
+ if (WIFSTOPPED (w)
+ && WSTOPSIG (w) == SYSCALL_SIGTRAP
+ && !gdb_catch_this_syscall_p (event_child))
+ {
+ if (debug_threads)
+ {
+ debug_printf ("Ignored syscall for LWP %ld.\n",
+ lwpid_of (current_thread));
+ }
+
+ linux_resume_one_lwp (event_child, event_child->stepping,
+ 0, NULL);
+ return ignore_event (ourstatus);
+ }
+
/* If GDB is not interested in this signal, don't stop other
threads, and don't report it to GDB. Just resume the inferior
right away. We do this for threading-related signals as well as
@@ -3559,8 +3680,16 @@ linux_wait_1 (ptid_t ptid,
}
}
- if (current_thread->last_resume_kind == resume_stop
- && WSTOPSIG (w) == SIGSTOP)
+ if (WSTOPSIG (w) == SYSCALL_SIGTRAP)
+ {
+ int sysret;
+
+ get_syscall_trapinfo (event_child,
+ &ourstatus->value.syscall_number, &sysret);
+ ourstatus->kind = event_child->syscall_state;
+ }
+ else if (current_thread->last_resume_kind == resume_stop
+ && WSTOPSIG (w) == SIGSTOP)
{
/* A thread that has been requested to stop by GDB with vCont;t,
and it stopped cleanly, so report as SIG0. The use of
@@ -4017,6 +4146,7 @@ linux_resume_one_lwp_throw (struct lwp_info *lwp,
struct thread_info *thread = get_lwp_thread (lwp);
struct thread_info *saved_thread;
int fast_tp_collecting;
+ int ptrace_request;
struct process_info *proc = get_thread_process (thread);
/* Note that target description may not be initialised
@@ -4204,7 +4334,14 @@ linux_resume_one_lwp_throw (struct lwp_info *lwp,
regcache_invalidate_thread (thread);
errno = 0;
lwp->stepping = step;
- ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, lwpid_of (thread),
+ if (step)
+ ptrace_request = PTRACE_SINGLESTEP;
+ else if (gdb_catching_syscalls_p (lwp))
+ ptrace_request = PTRACE_SYSCALL;
+ else
+ ptrace_request = PTRACE_CONT;
+ ptrace (ptrace_request,
+ lwpid_of (thread),
(PTRACE_TYPE_ARG3) 0,
/* Coerce to a uintptr_t first to avoid potential gcc warning
of coercing an 8 byte integer to a 4 byte pointer. */
@@ -6286,6 +6423,13 @@ linux_process_qsupported (char **features, int count)
}
static int
+linux_supports_catch_syscall (void)
+{
+ return (the_low_target.get_syscall_trapinfo != NULL
+ && linux_supports_tracesysgood());
+}
+
+static int
linux_supports_tracepoints (void)
{
if (*the_low_target.supports_tracepoints == NULL)
@@ -7209,7 +7353,8 @@ static struct target_ops linux_target_ops = {
linux_sw_breakpoint_from_kind,
linux_proc_tid_get_name,
linux_breakpoint_kind_from_current_state,
- linux_supports_software_single_step
+ linux_supports_software_single_step,
+ linux_supports_catch_syscall,
};
#ifdef HAVE_LINUX_REGSETS
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index e80c67f54097..751eea18eaf8 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -240,6 +240,12 @@ struct linux_target_ops
/* See target.h. */
int (*supports_hardware_single_step) (void);
+
+ /* Fill *SYSNO with the syscall nr trapped. Fill *SYSRET with the
+ return code. Only to be called when inferior is stopped
+ due to SYSCALL_SIGTRAP. */
+ void (*get_syscall_trapinfo) (struct regcache *regcache,
+ int *sysno, int *sysret);
};
extern struct linux_target_ops the_low_target;
@@ -278,6 +284,13 @@ struct lwp_info
event already received in a wait()). */
int stopped;
+ /* Signal whether we are in a SYSCALL_ENTRY or
+ in a SYSCALL_RETURN event.
+ Values:
+ - TARGET_WAITKIND_SYSCALL_ENTRY
+ - TARGET_WAITKIND_SYSCALL_RETURN */
+ enum target_waitkind syscall_state;
+
/* When stopped is set, the last wait status recorded for this lwp. */
int last_status;
diff --git a/gdb/gdbserver/linux-x86-low.c b/gdb/gdbserver/linux-x86-low.c
index 0432a86aab2b..4a01750c1724 100644
--- a/gdb/gdbserver/linux-x86-low.c
+++ b/gdb/gdbserver/linux-x86-low.c
@@ -1438,6 +1438,31 @@ x86_arch_setup (void)
current_process ()->tdesc = x86_linux_read_description ();
}
+/* Fill *SYSNO and *SYSRET with the syscall nr trapped and the syscall return
+ code. This should only be called if LWP got a SYSCALL_SIGTRAP. */
+
+static void
+x86_get_syscall_trapinfo (struct regcache *regcache, int *sysno, int *sysret)
+{
+ int use_64bit = register_size (regcache->tdesc, 0) == 8;
+
+ if (use_64bit)
+ {
+ long l_sysno;
+ long l_sysret;
+
+ collect_register_by_name (regcache, "orig_rax", &l_sysno);
+ collect_register_by_name (regcache, "rax", &l_sysret);
+ *sysno = (int) l_sysno;
+ *sysret = (int) l_sysret;
+ }
+ else
+ {
+ collect_register_by_name (regcache, "orig_eax", sysno);
+ collect_register_by_name (regcache, "eax", sysret);
+ }
+}
+
static int
x86_supports_tracepoints (void)
{
@@ -3315,6 +3340,7 @@ struct linux_target_ops the_low_target =
x86_supports_range_stepping,
NULL, /* breakpoint_kind_from_current_state */
x86_supports_hardware_single_step,
+ x86_get_syscall_trapinfo,
};
void
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index c5f4647052c0..ec69b63c32c6 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1132,6 +1132,8 @@ prepare_resume_reply (char *buf, ptid_t ptid,
case TARGET_WAITKIND_VFORK_DONE:
case TARGET_WAITKIND_EXECD:
case TARGET_WAITKIND_THREAD_CREATED:
+ case TARGET_WAITKIND_SYSCALL_ENTRY:
+ case TARGET_WAITKIND_SYSCALL_RETURN:
{
struct thread_info *saved_thread;
const char **regp;
@@ -1181,6 +1183,16 @@ prepare_resume_reply (char *buf, ptid_t ptid,
sprintf (buf, "T%02xcreate:;", signal);
}
+ else if (status->kind == TARGET_WAITKIND_SYSCALL_ENTRY
+ || status->kind == TARGET_WAITKIND_SYSCALL_RETURN)
+ {
+ enum gdb_signal signal = GDB_SIGNAL_TRAP;
+ const char *event = (status->kind == TARGET_WAITKIND_SYSCALL_ENTRY
+ ? "syscall_entry" : "syscall_return");
+
+ sprintf (buf, "T%02x%s:%x;", signal, event,
+ status->value.syscall_number);
+ }
else
sprintf (buf, "T%02x", status->value.sig);
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index fe7195ddab09..13fe159e4bef 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -624,6 +624,54 @@ handle_general_set (char *own_buf)
return;
}
+ if (startswith (own_buf, "QCatchSyscalls:"))
+ {
+ const char *p = own_buf + sizeof ("QCatchSyscalls:") - 1;
+ int enabled = -1;
+ CORE_ADDR sysno;
+ struct process_info *process;
+
+ if (!target_running () || !target_supports_catch_syscall ())
+ {
+ write_enn (own_buf);
+ return;
+ }
+
+ if (strcmp (p, "0") == 0)
+ enabled = 0;
+ else if (p[0] == '1' && (p[1] == ';' || p[1] == '\0'))
+ enabled = 1;
+ else
+ {
+ fprintf (stderr, "Unknown catch-syscalls mode requested: %s\n",
+ own_buf);
+ write_enn (own_buf);
+ return;
+ }
+
+ process = current_process ();
+ VEC_truncate (int, process->syscalls_to_catch, 0);
+
+ if (enabled)
+ {
+ p += 1;
+ if (*p == ';')
+ {
+ p += 1;
+ while (*p != '\0')
+ {
+ p = decode_address_to_semicolon (&sysno, p);
+ VEC_safe_push (int, process->syscalls_to_catch, (int) sysno);
+ }
+ }
+ else
+ VEC_safe_push (int, process->syscalls_to_catch, ANY_SYSCALL);
+ }
+
+ write_ok (own_buf);
+ return;
+ }
+
if (strcmp (own_buf, "QStartNoAckMode") == 0)
{
if (remote_debug)
@@ -2200,6 +2248,9 @@ handle_query (char *own_buf, int packet_len, int *new_packet_len_p)
"PacketSize=%x;QPassSignals+;QProgramSignals+",
PBUFSIZ - 1);
+ if (target_supports_catch_syscall ())
+ strcat (own_buf, ";QCatchSyscalls+");
+
if (the_target->qxfer_libraries_svr4 != NULL)
strcat (own_buf, ";qXfer:libraries-svr4:read+"
";augmented-libraries-svr4-read+");
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 58cf342ed368..3d78fb319514 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -134,4 +134,10 @@ extern void discard_queued_stop_replies (ptid_t ptid);
as large as the largest register set supported by gdbserver. */
#define PBUFSIZ 16384
+/* Definition for an unknown syscall, used basically in error-cases. */
+#define UNKNOWN_SYSCALL (-1)
+
+/* Definition for any syscall, used for unfiltered syscall reporting. */
+#define ANY_SYSCALL (-2)
+
#endif /* SERVER_H */
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index cbdb8d98d221..5af205139d1d 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -467,6 +467,10 @@ struct target_ops
/* Returns true if the target can software single step. */
int (*supports_software_single_step) (void);
+
+ /* Return 1 if the target supports catch syscall, 0 (or leave the
+ callback NULL) otherwise. */
+ int (*supports_catch_syscall) (void);
};
extern struct target_ops *the_target;
@@ -542,6 +546,10 @@ int kill_inferior (int);
the_target->process_qsupported (features, count); \
} while (0)
+#define target_supports_catch_syscall() \
+ (the_target->supports_catch_syscall ? \
+ (*the_target->supports_catch_syscall) () : 0)
+
#define target_supports_tracepoints() \
(the_target->supports_tracepoints \
? (*the_target->supports_tracepoints) () : 0)
diff --git a/gdb/linux-nat.h b/gdb/linux-nat.h
index 7bdeb314a663..73888e5f3364 100644
--- a/gdb/linux-nat.h
+++ b/gdb/linux-nat.h
@@ -88,7 +88,7 @@ struct lwp_info
or to a local variable in lin_lwp_wait. */
struct target_waitstatus waitstatus;
- /* Signal wether we are in a SYSCALL_ENTRY or
+ /* Signal whether we are in a SYSCALL_ENTRY or
in a SYSCALL_RETURN event.
Values:
- TARGET_WAITKIND_SYSCALL_ENTRY
diff --git a/gdb/remote.c b/gdb/remote.c
index 528d863762cc..e825d27d8405 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -1392,6 +1392,7 @@ enum {
PACKET_qSupported,
PACKET_qTStatus,
PACKET_QPassSignals,
+ PACKET_QCatchSyscalls,
PACKET_QProgramSignals,
PACKET_qCRC,
PACKET_qSearch_memory,
@@ -1987,6 +1988,93 @@ remote_pass_signals (struct target_ops *self,
}
}
+/* If 'QCatchSyscalls' is supported, tell the remote stub
+ to report syscalls to GDB. */
+
+static int
+remote_set_syscall_catchpoint (struct target_ops *self,
+ int pid, int needed, int any_count,
+ int table_size, int *table)
+{
+ char *catch_packet;
+ enum packet_result result;
+ int n_sysno = 0;
+
+ if (packet_support (PACKET_QCatchSyscalls) == PACKET_DISABLE)
+ {
+ /* Not supported. */
+ return 1;
+ }
+
+ if (needed && !any_count)
+ {
+ int i;
+
+ /* Count how many syscalls are to be caught (table[sysno] != 0). */
+ for (i = 0; i < table_size; i++)
+ {
+ if (table[i] != 0)
+ n_sysno++;
+ }
+ }
+
+ if (remote_debug)
+ {
+ fprintf_unfiltered (gdb_stdlog,
+ "remote_set_syscall_catchpoint "
+ "pid %d needed %d any_count %d n_sysno %d\n",
+ pid, needed, any_count, n_sysno);
+ }
+
+ if (needed)
+ {
+ /* Prepare a packet with the sysno list, assuming max 8+1
+ characters for a sysno. If the resulting packet size is too
+ big, fallback on the non-selective packet. */
+ const int maxpktsz = strlen ("QCatchSyscalls:1") + n_sysno * 9 + 1;
+
+ catch_packet = xmalloc (maxpktsz);
+ strcpy (catch_packet, "QCatchSyscalls:1");
+ if (!any_count)
+ {
+ int i;
+ char *p;
+
+ p = catch_packet;
+ p += strlen (p);
+
+ /* Add in catch_packet each syscall to be caught (table[i] != 0). */
+ for (i = 0; i < table_size; i++)
+ {
+ if (table[i] != 0)
+ p += xsnprintf (p, catch_packet + maxpktsz - p, ";%x", i);
+ }
+ }
+ if (strlen (catch_packet) > get_remote_packet_size ())
+ {
+ /* catch_packet too big. Fallback to less efficient
+ non selective mode, with GDB doing the filtering. */
+ catch_packet[sizeof ("QCatchSyscalls:1") - 1] = 0;
+ }
+ }
+ else
+ catch_packet = xstrdup ("QCatchSyscalls:0");
+
+ {
+ struct cleanup *old_chain = make_cleanup (xfree, catch_packet);
+ struct remote_state *rs = get_remote_state ();
+
+ putpkt (catch_packet);
+ getpkt (&rs->buf, &rs->buf_size, 0);
+ result = packet_ok (rs->buf, &remote_protocol_packets[PACKET_QCatchSyscalls]);
+ do_cleanups (old_chain);
+ if (result == PACKET_OK)
+ return 0;
+ else
+ return -1;
+ }
+}
+
/* If 'QProgramSignals' is supported, tell the remote stub what
signals it should pass through to the inferior when detaching. */
@@ -4467,6 +4555,8 @@ static const struct protocol_feature remote_protocol_features[] = {
PACKET_qXfer_traceframe_info },
{ "QPassSignals", PACKET_DISABLE, remote_supported_packet,
PACKET_QPassSignals },
+ { "QCatchSyscalls", PACKET_DISABLE, remote_supported_packet,
+ PACKET_QCatchSyscalls },
{ "QProgramSignals", PACKET_DISABLE, remote_supported_packet,
PACKET_QProgramSignals },
{ "QStartNoAckMode", PACKET_DISABLE, remote_supported_packet,
@@ -6371,6 +6461,22 @@ Packet: '%s'\n"),
if (strprefix (p, p1, "thread"))
event->ptid = read_ptid (++p1, &p);
+ else if (strprefix (p, p1, "syscall_entry"))
+ {
+ ULONGEST sysno;
+
+ event->ws.kind = TARGET_WAITKIND_SYSCALL_ENTRY;
+ p = unpack_varlen_hex (++p1, &sysno);
+ event->ws.value.syscall_number = (int) sysno;
+ }
+ else if (strprefix (p, p1, "syscall_return"))
+ {
+ ULONGEST sysno;
+
+ event->ws.kind = TARGET_WAITKIND_SYSCALL_RETURN;
+ p = unpack_varlen_hex (++p1, &sysno);
+ event->ws.value.syscall_number = (int) sysno;
+ }
else if (strprefix (p, p1, "watch")
|| strprefix (p, p1, "rwatch")
|| strprefix (p, p1, "awatch"))
@@ -12976,6 +13082,7 @@ Specify the serial device it is connected to\n\
remote_ops.to_load = remote_load;
remote_ops.to_mourn_inferior = remote_mourn;
remote_ops.to_pass_signals = remote_pass_signals;
+ remote_ops.to_set_syscall_catchpoint = remote_set_syscall_catchpoint;
remote_ops.to_program_signals = remote_program_signals;
remote_ops.to_thread_alive = remote_thread_alive;
remote_ops.to_thread_name = remote_thread_name;
@@ -13542,6 +13649,9 @@ Show the maximum size of the address (in bits) in a memory packet."), NULL,
add_packet_config_cmd (&remote_protocol_packets[PACKET_QPassSignals],
"QPassSignals", "pass-signals", 0);
+ add_packet_config_cmd (&remote_protocol_packets[PACKET_QCatchSyscalls],
+ "QCatchSyscalls", "catch-syscalls", 0);
+
add_packet_config_cmd (&remote_protocol_packets[PACKET_QProgramSignals],
"QProgramSignals", "program-signals", 0);
diff --git a/gdb/testsuite/gdb.base/catch-syscall.c b/gdb/testsuite/gdb.base/catch-syscall.c
index e65d4a4906c3..98222fa78819 100644
--- a/gdb/testsuite/gdb.base/catch-syscall.c
+++ b/gdb/testsuite/gdb.base/catch-syscall.c
@@ -31,13 +31,20 @@ int write_syscall = SYS_write;
int unknown_syscall = 123456789;
int exit_group_syscall = SYS_exit_group;
+/* Set by the test when it wants execve. */
+int do_execve = 0;
+
int
-main (void)
+main (int argc, char *const argv[])
{
int fd[2];
char buf1[2] = "a";
char buf2[2];
+ /* Test a simple self-exec, but only on request. */
+ if (do_execve)
+ execv (*argv, argv);
+
/* A close() with a wrong argument. We are only
interested in the syscall. */
close (-1);
diff --git a/gdb/testsuite/gdb.base/catch-syscall.exp b/gdb/testsuite/gdb.base/catch-syscall.exp
index 26fb6a513a42..70a75553e945 100644
--- a/gdb/testsuite/gdb.base/catch-syscall.exp
+++ b/gdb/testsuite/gdb.base/catch-syscall.exp
@@ -19,7 +19,7 @@
# It was written by Sergio Durigan Junior <sergiodj@linux.vnet.ibm.com>
# on September/2008.
-if { [is_remote target] || ![isnative] } then {
+if { ![isnative] } then {
continue
}
@@ -322,6 +322,32 @@ proc test_catch_syscall_mid_vfork {} {
}
}
+proc test_catch_syscall_execve {} {
+ global gdb_prompt decimal
+
+ with_test_prefix "execve" {
+
+ # Tell the test program we want an execve.
+ gdb_test_no_output "set do_execve = 1"
+
+ # Check for entry/return across the execve, making sure that the
+ # syscall_state isn't lost when turning into a new process.
+ insert_catch_syscall_with_arg "execve"
+ check_continue "execve"
+
+ # Remotes that don't track exec may report the raw SIGTRAP for it.
+ # If we use stepi now, we'll get a consistent trap for all targets.
+ gdb_test "stepi" ".*" "step after execve"
+
+ # Continue to main so extended-remote can read files as needed.
+ # (Otherwise that "Reading" output confuses gdb_continue_to_end.)
+ gdb_continue "main"
+
+ # Now can we finish?
+ check_for_program_end
+ }
+}
+
proc test_catch_syscall_fail_nodatadir {} {
with_test_prefix "fail no datadir" {
# Sanitizing.
@@ -392,6 +418,9 @@ proc do_syscall_tests {} {
# Testing the 'catch syscall' command starting mid-vfork.
if [runto_main] then { test_catch_syscall_mid_vfork }
+ # Testing that 'catch syscall' entry/return tracks across execve.
+ if [runto_main] then { test_catch_syscall_execve }
+
# Testing if the 'catch syscall' command works when switching to
# different architectures on-the-fly (PR gdb/10737).
if [runto_main] then { test_catch_syscall_multi_arch }
--
2.5.0