Implement 'catch syscall' for gdbserver
[external/binutils.git] / gdb / gdbserver / linux-low.c
index 47f0f9d..7c77a2f 100644 (file)
@@ -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,14 @@ 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.  */
+      event_lwp->syscall_state = TARGET_WAITKIND_SYSCALL_ENTRY;
+
+      /* Restore the list to catch.  Don't rely on the client, which is free
+        to avoid sending a new list when the architecture doesn't change.
+        Also, for ANY_SYSCALL, the architecture doesn't really matter.  */
+      proc->syscalls_to_catch = syscalls_to_catch;
+
       /* Report the event.  */
       *orig_event_lwp = event_lwp;
       return 0;
@@ -682,6 +700,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 +2288,8 @@ linux_low_ptrace_options (int attached)
   if (report_exec_events)
     options |= PTRACE_O_TRACEEXEC;
 
+  options |= PTRACE_O_TRACESYSGOOD;
+
   return options;
 }
 
@@ -2364,6 +2418,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 +3042,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 +3414,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 +3682,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 +4148,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 +4336,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 +6425,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 +7355,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