* Let GDB user modify the signal to send to the guest process
* implement qXfer:siginfo:read: packet to allow GDB to show $_siginfo



git-svn-id: svn://svn.valgrind.org/valgrind/trunk@15248 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/NEWS b/NEWS
index 6531623..3c3fb2c 100644
--- a/NEWS
+++ b/NEWS
@@ -49,6 +49,13 @@
   compiler version is 14.0 or later.
 
 * New and modified GDB server monitor features:
+  - When a signal is reported in GDB, you can now use the GDB convenience
+    variable $_siginfo to examine detailed signal information.
+ 
+  - Valgrind gdbserver now allows the user to change the signal
+    to deliver to the process. So, use 'signal SIGNAL' to continue execution
+    with SIGNAL instead of the signal reported to GDB. Use 'signal 0' to
+    continue without passing the signal to the process.
 
   - With recent GDB (>= 7.9.50.20150514-cvs), the command 'target remote'
     will automatically load the executable file of the process running
diff --git a/coregrind/m_gdbserver/m_gdbserver.c b/coregrind/m_gdbserver/m_gdbserver.c
index 2238ca5..888340a 100644
--- a/coregrind/m_gdbserver/m_gdbserver.c
+++ b/coregrind/m_gdbserver/m_gdbserver.c
@@ -934,16 +934,24 @@
    return ret;
 }
 
-
-void VG_(gdbserver_report_fatal_signal) (Int vki_sigNo, ThreadId tid)
+static void dlog_signal (const HChar *who, const vki_siginfo_t *info,
+                         ThreadId tid)
 {
-   dlog(1, "VG core calling VG_(gdbserver_report_fatal_signal) "
+   dlog(1, "VG core calling %s "
         "vki_nr %d %s gdb_nr %d %s tid %d\n", 
-        vki_sigNo, VG_(signame)(vki_sigNo),
-        target_signal_from_host (vki_sigNo),
-        target_signal_to_name(target_signal_from_host (vki_sigNo)), 
+        who,
+        info->si_signo, VG_(signame)(info->si_signo),
+        target_signal_from_host (info->si_signo),
+        target_signal_to_name(target_signal_from_host (info->si_signo)), 
         tid);
 
+}
+
+void VG_(gdbserver_report_fatal_signal) (const vki_siginfo_t *info,
+                                         ThreadId tid)
+{
+   dlog_signal("VG_(gdbserver_report_fatal_signal)", info, tid);
+
    if (remote_connected()) {
       dlog(1, "already connected, assuming already reported\n");
       return;
@@ -952,21 +960,16 @@
    VG_(umsg)("(action on fatal signal) vgdb me ... \n");
 
    /* indicate to gdbserver that there is a signal */
-   gdbserver_signal_encountered (vki_sigNo);
+   gdbserver_signal_encountered (info);
 
    /* let gdbserver do some work, e.g. show the signal to the user */
    call_gdbserver (tid, signal_reason);
    
 }
 
-Bool VG_(gdbserver_report_signal) (Int vki_sigNo, ThreadId tid)
+Bool VG_(gdbserver_report_signal) (vki_siginfo_t *info, ThreadId tid)
 {
-   dlog(1, "VG core calling VG_(gdbserver_report_signal) "
-        "vki_nr %d %s gdb_nr %d %s tid %d\n", 
-        vki_sigNo, VG_(signame)(vki_sigNo),
-        target_signal_from_host (vki_sigNo),
-        target_signal_to_name(target_signal_from_host (vki_sigNo)), 
-        tid);
+   dlog_signal("VG_(gdbserver_report_signal)", info, tid);
 
    /* if gdbserver is currently not connected, then signal
       is to be given to the process */
@@ -977,19 +980,20 @@
    /* if gdb has informed gdbserver that this signal can be
       passed directly without informing gdb, then signal is
       to be given to the process. */
-   if (pass_signals[target_signal_from_host(vki_sigNo)]) {
+   if (pass_signals[target_signal_from_host(info->si_signo)]) {
       dlog(1, "pass_signals => pass\n");
       return True;
    }
    
    /* indicate to gdbserver that there is a signal */
-   gdbserver_signal_encountered (vki_sigNo);
+   gdbserver_signal_encountered (info);
 
-   /* let gdbserver do some work, e.g. show the signal to the user */
+   /* let gdbserver do some work, e.g. show the signal to the user.
+      User can also decide to ignore the signal or change the signal. */
    call_gdbserver (tid, signal_reason);
    
    /* ask gdbserver what is the final decision */
-   if (gdbserver_deliver_signal (vki_sigNo)) {
+   if (gdbserver_deliver_signal (info)) {
       dlog(1, "gdbserver deliver signal\n");
       return True;
    } else {
diff --git a/coregrind/m_gdbserver/server.c b/coregrind/m_gdbserver/server.c
index 6fb507d..6361c5a 100644
--- a/coregrind/m_gdbserver/server.c
+++ b/coregrind/m_gdbserver/server.c
@@ -857,8 +857,10 @@
          }
          VG_(lseek) (fd, ofs, VKI_SEEK_SET);
          len_read = VG_(read) (fd, toread, len);
-         *new_packet_len_p = write_qxfer_response (arg_own_buf, (unsigned char *)toread,
-                                                   len_read, ofs + len_read < doc_len);
+         *new_packet_len_p = write_qxfer_response (arg_own_buf,
+                                                   (unsigned char *)toread,
+                                                   len_read,
+                                                   ofs + len_read < doc_len);
          VG_(close) (fd);
          return;
       }
@@ -878,8 +880,8 @@
          return;
       }
 
-      if (len > PBUFSIZ - 2)
-         len = PBUFSIZ - 2;
+      if (len > PBUFSIZ - POVERHSIZ)
+         len = PBUFSIZ - POVERHSIZ;
       data = malloc (len);
 
       {
@@ -925,12 +927,13 @@
       unsigned long pid;
       const HChar *name;
 
-      /* Reject any annex; grab the offset and length.  */
+      /* grab the annex, offset and length.  */
       if (decode_xfer_read (arg_own_buf + 21, &annex, &ofs, &len) < 0) {
          strcpy (arg_own_buf, "E00");
          return;
       }
       
+      /* Reject any annex with invalid/unexpected pid */
       if (strlen(annex) > 0)
          pid = strtoul (annex, NULL, 16);
       else
@@ -974,6 +977,44 @@
       return;
    }
 
+   if (strncmp ("qXfer:siginfo:read:", arg_own_buf, 19) == 0) {
+      vki_siginfo_t info;
+      int n;
+      CORE_ADDR ofs;
+      unsigned int len;
+      const char *annex;
+
+      /* Reject any annex; grab the offset and length.  */
+      if (decode_xfer_read (arg_own_buf + 19, &annex, &ofs, &len) < 0
+          || annex[0] != '\0') {
+         strcpy (arg_own_buf, "E00");
+         return;
+      }
+      
+      if (len > PBUFSIZ - POVERHSIZ)
+         len = PBUFSIZ - POVERHSIZ;
+
+      gdbserver_pending_signal_to_report(&info);
+
+      if (ofs >= sizeof(info))
+         n = -1;
+      else
+         n = sizeof(info) - ofs;
+
+      if (n < 0)
+         write_enn (arg_own_buf);
+      else if (n > len)
+         *new_packet_len_p = write_qxfer_response (arg_own_buf, 
+                                                   (unsigned char *)&info,
+                                                   len, 1);
+      else
+         *new_packet_len_p = write_qxfer_response (arg_own_buf, 
+                                                   (unsigned char *)&info,
+                                                   n, 0);
+      
+      return;
+   }
+
    /* Protocol features query.  */
    if (strncmp ("qSupported", arg_own_buf, 10) == 0
        && (arg_own_buf[10] == ':' || arg_own_buf[10] == '\0')) {
@@ -1000,6 +1041,7 @@
          initialize_shadow_low(False);
       }
       strcat (arg_own_buf, ";qXfer:exec-file:read+");
+      strcat (arg_own_buf, ";qXfer:siginfo:read+");
       return;
    }
 
@@ -1097,7 +1139,7 @@
          putpkt (own_buf);
       }
 
-      /* If we our status is terminal (exit or fatal signal) get out
+      /* If our status is terminal (exit or fatal signal) get out
          as quickly as we can. We won't be able to handle any request
          anymore.  */
       if (status == 'W' || status == 'X') {
diff --git a/coregrind/m_gdbserver/server.h b/coregrind/m_gdbserver/server.h
index 1dd0d1a..d2bbfd7 100644
--- a/coregrind/m_gdbserver/server.h
+++ b/coregrind/m_gdbserver/server.h
@@ -206,11 +206,17 @@
    A call to call_gdbserver is needed to send the resume reply to GDB.
    After this call, gdbserver_deliver_signal indicates if the signal
    is effectively to be delivered to the guest process. */
-extern void gdbserver_signal_encountered (Int vki_sigNo);
-/* between these two calls, call call_gdbserver */
+extern void gdbserver_signal_encountered (const vki_siginfo_t *info);
+/* between these two calls, call call_gdbserver.
+   Between these 2 calls the signal to report to GDB can be retrieved using
+   gdbserver_pending_signal_to_report. */
 /* If gdbserver_deliver_signal True, then gdb did not ask
    to ignore the signal, so signal can be delivered to the guest. */
-extern Bool gdbserver_deliver_signal (Int vki_sigNo);
+extern Bool gdbserver_deliver_signal (vki_siginfo_t *info);
+
+/* Signal info last provided with gdbserver_signal_encountered.
+   It is what is/will be reported to GDB. */
+extern void gdbserver_pending_signal_to_report (vki_siginfo_t /* OUT */ *info);
 
 /* Called when a process is about to go with reason ('W' or 'X') and code.
    This sets global variables that will be used to return the process
diff --git a/coregrind/m_gdbserver/target.c b/coregrind/m_gdbserver/target.c
index 1dc6e41..64ce5a3 100644
--- a/coregrind/m_gdbserver/target.c
+++ b/coregrind/m_gdbserver/target.c
@@ -153,17 +153,28 @@
 */
 static CORE_ADDR resume_pc;
 
-static int vki_signal_to_report;
+static vki_siginfo_t vki_signal_to_report;
+static vki_siginfo_t vki_signal_to_deliver;
 
-void gdbserver_signal_encountered (Int vki_sigNo)
+void gdbserver_signal_encountered (const vki_siginfo_t *info)
 {
-   vki_signal_to_report = vki_sigNo;
+   vki_signal_to_report = *info;
+   vki_signal_to_deliver = *info;
 }
 
-static int vki_signal_to_deliver;
-Bool gdbserver_deliver_signal (Int vki_sigNo)
+void gdbserver_pending_signal_to_report (vki_siginfo_t *info)
 {
-   return vki_sigNo == vki_signal_to_deliver;
+   *info = vki_signal_to_report;
+}
+
+Bool gdbserver_deliver_signal (vki_siginfo_t *info)
+{
+   if (info->si_signo != vki_signal_to_deliver.si_signo)
+      dlog(1, "GDB changed signal  info %d to_report %d to_deliver %d\n",
+           info->si_signo, vki_signal_to_report.si_signo,
+           vki_signal_to_deliver.si_signo);
+   *info = vki_signal_to_deliver;
+   return vki_signal_to_deliver.si_signo != 0;
 }
 
 static unsigned char exit_status_to_report;
@@ -238,7 +249,10 @@
            C2v(stopped_data_address));
       VG_(set_watchpoint_stop_address) ((Addr) 0);
    }
-   vki_signal_to_deliver = resume_info->sig;
+   vki_signal_to_deliver.si_signo = resume_info->sig;
+   /* signal was reported to GDB, GDB told us to resume execution.
+      So, reset the signal to report to 0. */
+   VG_(memset) (&vki_signal_to_report, 0, sizeof(vki_signal_to_report));
    
    stepping = resume_info->step;
    resume_pc = (*the_low_target.get_pc) ();
@@ -288,12 +302,10 @@
       and with a signal TRAP (i.e. a breakpoint), unless there is
       a signal to report. */
    *ourstatus = 'T';
-   if (vki_signal_to_report == 0)
+   if (vki_signal_to_report.si_signo == 0)
       sig = TARGET_SIGNAL_TRAP;
-   else {
-      sig = target_signal_from_host(vki_signal_to_report);
-      vki_signal_to_report = 0;
-   }
+   else
+      sig = target_signal_from_host(vki_signal_to_report.si_signo);
    
    if (vgdb_interrupted_tid != 0)
       tst = VG_(get_ThreadState) (vgdb_interrupted_tid);
diff --git a/coregrind/m_signals.c b/coregrind/m_signals.c
index abfa0d7..16d220c 100644
--- a/coregrind/m_signals.c
+++ b/coregrind/m_signals.c
@@ -694,12 +694,15 @@
 
 /* returns True if signal is to be ignored. 
    To check this, possibly call gdbserver with tid. */
-static Bool is_sig_ign(Int sigNo, ThreadId tid)
+static Bool is_sig_ign(vki_siginfo_t *info, ThreadId tid)
 {
-   vg_assert(sigNo >= 1 && sigNo <= _VKI_NSIG);
+   vg_assert(info->si_signo >= 1 && info->si_signo <= _VKI_NSIG);
 
-   return scss.scss_per_sig[sigNo].scss_handler == VKI_SIG_IGN
-      || !VG_(gdbserver_report_signal) (sigNo, tid);
+   /* If VG_(gdbserver_report_signal) tells to report the signal,
+      then verify if this signal is not to be ignored. GDB might have
+      modified si_signo, so we check after the call to gdbserver. */
+   return !VG_(gdbserver_report_signal) (info, tid)
+      || scss.scss_per_sig[info->si_signo].scss_handler == VKI_SIG_IGN;
 }
 
 /* ---------------------------------------------------------------------
@@ -1786,7 +1789,7 @@
        && VG_(dyn_vgdb_error) <= VG_(get_n_errs_shown)() + 1) {
       /* Note: we add + 1 to n_errs_shown as the fatal signal was not
          reported through error msg, and so was not counted. */
-      VG_(gdbserver_report_fatal_signal) (sigNo, tid);
+      VG_(gdbserver_report_fatal_signal) (info, tid);
    }
 
    if (VG_(is_action_requested)( "Attach to debugger", & VG_(clo_db_attach) )) {
@@ -1922,7 +1925,7 @@
 
    /* Even if gdbserver indicates to ignore the signal, we must deliver it.
       So ignore the return value of VG_(gdbserver_report_signal). */
-   (void) VG_(gdbserver_report_signal) (VKI_SIGSEGV, tid);
+   (void) VG_(gdbserver_report_signal) (&info, tid);
 
    /* If they're trying to block the signal, force it to be delivered */
    if (VG_(sigismember)(&VG_(threads)[tid].sig_mask, VKI_SIGSEGV))
@@ -1962,7 +1965,7 @@
    info.si_code  = VKI_ILL_ILLOPC; /* jrs: no idea what this should be */
    info.VKI_SIGINFO_si_addr = (void*)addr;
 
-   if (VG_(gdbserver_report_signal) (VKI_SIGILL, tid)) {
+   if (VG_(gdbserver_report_signal) (&info, tid)) {
       resume_scheduler(tid);
       deliver_signal(tid, &info, NULL);
    }
@@ -1987,7 +1990,7 @@
       in .si_addr.  Oh well. */
    /* info.VKI_SIGINFO_si_addr = (void*)addr; */
 
-   if (VG_(gdbserver_report_signal) (VKI_SIGBUS, tid)) {
+   if (VG_(gdbserver_report_signal) (&info, tid)) {
       resume_scheduler(tid);
       deliver_signal(tid, &info, NULL);
    }
@@ -2027,7 +2030,7 @@
 #  endif
 
    /* fixs390: do we need to do anything here for s390 ? */
-   if (VG_(gdbserver_report_signal) (VKI_SIGTRAP, tid)) {
+   if (VG_(gdbserver_report_signal) (&info, tid)) {
       resume_scheduler(tid);
       deliver_signal(tid, &info, &uc);
    }
@@ -2236,7 +2239,7 @@
 
    /* (2) */
    /* Set up the thread's state to deliver a signal */
-   if (!is_sig_ign(info->si_signo, tid))
+   if (!is_sig_ign(info, tid))
       deliver_signal(tid, info, uc);
 
    /* It's crucial that (1) and (2) happen in the order (1) then (2)
@@ -2508,7 +2511,7 @@
       }
 
       if (VG_(in_generated_code)) {
-         if (VG_(gdbserver_report_signal) (sigNo, tid)
+         if (VG_(gdbserver_report_signal) (info, tid)
              || VG_(sigismember)(&tst->sig_mask, sigNo)) {
             /* Can't continue; must longjmp back to the scheduler and thus
                enter the sighandler immediately. */
@@ -2714,7 +2717,7 @@
       /* OK, something to do; deliver it */
       if (VG_(clo_trace_signals))
          VG_(dmsg)("Polling found signal %d for tid %d\n", sip->si_signo, tid);
-      if (!is_sig_ign(sip->si_signo, tid))
+      if (!is_sig_ign(sip, tid))
 	 deliver_signal(tid, sip, NULL);
       else if (VG_(clo_trace_signals))
          VG_(dmsg)("   signal %d ignored\n", sip->si_signo);
diff --git a/coregrind/pub_core_gdbserver.h b/coregrind/pub_core_gdbserver.h
index c7bcd97..6ec9daf 100644
--- a/coregrind/pub_core_gdbserver.h
+++ b/coregrind/pub_core_gdbserver.h
@@ -105,14 +105,19 @@
 // no gdb is connected, or gdb instructs to pass the signal.
 // Note that if the below returns True, the signal might
 // still be ignored if this is the action desired by the
-// guest program.
-extern Bool VG_(gdbserver_report_signal) (Int vki_signo, ThreadId tid);
+// guest program. Using GDB, the user can also modify the signal to be
+// reported (e.g. changing the signo to pass to the guest).
+// If this function returns True, m_signals.c should deliver the signal
+// info as modified by VG_(gdbserver_report_signal).
+// If this function returns False, no signal should be reported.
+extern Bool VG_(gdbserver_report_signal) (vki_siginfo_t *info, ThreadId tid);
 
 // If no gdb is connected yet, wait for a gdb to connect and report
 // this (supposedly) fatal signal.
 // If a gdb is already connected, this does nothing (as normally 
 // the signal was already reported to the already connected gdb).
-extern void VG_(gdbserver_report_fatal_signal) (Int vki_signo, ThreadId tid);
+extern void VG_(gdbserver_report_fatal_signal) (const vki_siginfo_t *info,
+                                                ThreadId tid);
 
 /* Entry point invoked by scheduler.c to execute the request 
    VALGRIND_CLIENT_MONITOR_COMMAND.