vgdb must not transmit signals when gdbserver has been ptrace-invoked.

Most of the time, Valgrind masks async signals, and polls for such
signals at regular interval.
There is a very narrow range of code (around client syscall logic)
where such signals are unmasked (as they must be able to interrupt
syscalls).
This is the only range of code where Valgrind is expecting to
receive such a signal.

When gdbserver is ptraced invoked, Valgrind is artificially made
to jump out of this code portion. Signals are not masked.
When ptraceing valgrind, vgdb will get these signals but cannot
transmit them immediately, otherwise they arrive in range
of code where they are not expected (causing internal error
in syscall logic) and/or causing gdbserver syscalls to be
interrupted.

3 solutions to solve that were looked at:
1. have the gdbserver code masking signals "as quickly as possible".
 Easy to implement, but this leaves still a small window
 of code where signals are not masked and would cause a problem.
2. have vgdb setting the SIGMASK of valgrind before invoking
  gdbserver.
  This would be easy to implement, but changing the SIGMASK
  of the ptrace-d process is only available on very recent kernels.
3. have vgdb queuing signals, and only transmitting them once
   gdbserver invocation is finished.
This 3rd solution has been implemented.



git-svn-id: svn://svn.valgrind.org/valgrind/trunk@13896 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/vgdb-invoker-ptrace.c b/coregrind/vgdb-invoker-ptrace.c
index 71f88b7..b0e49a1 100644
--- a/coregrind/vgdb-invoker-ptrace.c
+++ b/coregrind/vgdb-invoker-ptrace.c
@@ -60,8 +60,18 @@
 # error "unexpected wordsize"
 #endif
 
+// if > 0, pid for which registers have to be restored.
+// if == 0, means we have not yet called setregs (or have already
+// restored the registers).
+static int pid_of_save_regs = 0;
 /* True if we have continued pid_of_save_regs after PTRACE_ATTACH. */
 static Bool pid_of_save_regs_continued = False;
+// When setregs has been called to change the registers of pid_of_save_regs,
+// vgdb cannot transmit the signals intercepted during ptrace.
+// So, we queue them, and will deliver them when detaching.
+// See function waitstopped for more info.
+static int signal_queue_sz = 0;
+static siginfo_t *signal_queue;
 
 /* True when loss of connection indicating that the Valgrind
    process is dying. */
@@ -252,9 +262,44 @@
          break;
 
       /* pid received a signal which is not the signal we are waiting for.
-         We continue pid, transmitting this signal. */
-      DEBUG(1, "waitstopped PTRACE_CONT with signal %d\n", signal_received);
-      res = ptrace (PTRACE_CONT, pid, NULL, signal_received);
+         If we have not (yet) changed the registers of the inferior
+         or we have (already) reset them, we can transmit the signal.
+
+         If we have already set the registers of the inferior, we cannot
+         transmit the signal, as this signal would arrive when the
+         gdbserver code runs. And valgrind only expects signals to
+         arrive in a small code portion around
+         client syscall logic, where signal are unmasked (see e.g.
+         m_syswrap/syscall-x86-linux.S ML_(do_syscall_for_client_WRK).
+
+         As ptrace is forcing a call to gdbserver by jumping
+         'out of this region', signals are not masked, but
+         will arrive outside of the allowed/expected code region.
+         So, if we have changed the registers of the inferior, we
+         rather queue the signal to transmit them when detaching,
+         after having restored the registers to the initial values. */
+      if (pid_of_save_regs) {
+         siginfo_t *newsiginfo;
+
+         // realloc a bigger queue, and store new signal at the end.
+         // This is not very efficient but we assume not many sigs are queued.
+         signal_queue_sz++;
+         signal_queue = vrealloc(signal_queue, sizeof(siginfo_t) * signal_queue_sz);
+         newsiginfo = signal_queue + (signal_queue_sz - 1);
+
+         res = ptrace (PTRACE_GETSIGINFO, pid, NULL, newsiginfo);
+         if (res != 0) {
+            ERROR(errno, "PTRACE_GETSIGINFO failed: signal lost !!!!\n");
+            signal_queue_sz--;
+         } else
+            DEBUG(1, "waitstopped PTRACE_CONT, queuing signal %d"
+                  " si_signo %d si_pid %d\n",
+                  signal_received, newsiginfo->si_signo, newsiginfo->si_pid);
+         res = ptrace (PTRACE_CONT, pid, NULL, 0);
+      } else {
+         DEBUG(1, "waitstopped PTRACE_CONT with signal %d\n", signal_received);
+         res = ptrace (PTRACE_CONT, pid, NULL, signal_received);
+      }
       if (res != 0) {
          ERROR(errno, "waitstopped PTRACE_CONT\n");
          return False;
@@ -447,8 +492,6 @@
    }
 }
 
-// if > 0, pid for which registers have to be restored.
-static int pid_of_save_regs = 0;
 static struct user user_save;
 
 // The below indicates if ptrace_getregs (and ptrace_setregs) can be used.
@@ -588,6 +631,11 @@
 static
 void restore_and_detach (pid_t pid)
 {
+   int res;
+
+   DEBUG(1, "restore_and_detach pid %d pid_of_save_regs %d\n",
+         pid, pid_of_save_regs);
+
    if (pid_of_save_regs) {
       /* In case the 'main pid' has been continued, we need to stop it
          before resetting the registers. */
@@ -602,10 +650,32 @@
          ERROR(errno, "setregs restore registers pid %d after cont\n",
                pid_of_save_regs);
       }
+
+      /* Now, we transmit all the signals we have queued. */
+      if (signal_queue_sz > 0) {
+         int i;
+         for (i = 0; i < signal_queue_sz; i++) {
+            DEBUG(1, "PTRACE_CONT to transmit queued signal %d\n",
+                  signal_queue[i].si_signo);
+            res = ptrace (PTRACE_CONT, pid_of_save_regs, NULL,
+                          signal_queue[i].si_signo);
+            if (res != 0)
+               ERROR(errno, "PTRACE_CONT with signal %d\n",
+                     signal_queue[i].si_signo);
+            if (!stop(pid_of_save_regs, "sigstop after transmit sig"))
+               DEBUG(0, "Could not sigstop after transmit sig");
+         }
+         free (signal_queue);
+         signal_queue = NULL;
+         signal_queue_sz = 0;
+      }
       pid_of_save_regs = 0;
    } else {
       DEBUG(1, "PTRACE_SETREGS restore registers: no pid\n");
    }
+   if (signal_queue)
+      ERROR (0, "One or more signals queued were not delivered. "
+             "First signal: %d\n", signal_queue);
    detach_from_all_threads(pid);
 }