From patchwork Sat Jan 9 03:09:14 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Josh Stone X-Patchwork-Id: 10308 Received: (qmail 80603 invoked by alias); 9 Jan 2016 03:09:58 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 80567 invoked by uid 89); 9 Jan 2016 03:09:55 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.2 required=5.0 tests=AWL, BAYES_50, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=UD:catch-syscall.exp, mid-vfork, midvfork, 123456789 X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Sat, 09 Jan 2016 03:09:51 +0000 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (Postfix) with ESMTPS id 900B4691; Sat, 9 Jan 2016 03:09:49 +0000 (UTC) Received: from moya.redhat.com (ovpn-113-34.phx2.redhat.com [10.3.113.34]) by int-mx10.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u0939QuY032284; Fri, 8 Jan 2016 22:09:31 -0500 From: Josh Stone To: gdb-patches@sourceware.org Cc: philippe.waroquiers@skynet.be, sergiodj@redhat.com, palves@redhat.com, eliz@gnu.org, xdje42@gmail.com, scox@redhat.com, Josh Stone Subject: [PATCH v4] Implement 'catch syscall' for gdbserver Date: Fri, 8 Jan 2016 19:09:14 -0800 Message-Id: <1452308954-13679-1-git-send-email-jistone@redhat.com> In-Reply-To: <1449196006-13759-2-git-send-email-jistone@redhat.com> References: <1449196006-13759-2-git-send-email-jistone@redhat.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 Philippe Waroquiers * 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) : Comment typo. gdb/doc/ChangeLog: 2016-01-08 Josh Stone Philippe Waroquiers * 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 Philippe Waroquiers * 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 Philippe Waroquiers * 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 # 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 }