summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--linux-user/qemu.h1
-rw-r--r--linux-user/signal.c62
2 files changed, 62 insertions, 1 deletions
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 4d24e74775..36d4a738ea 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -126,6 +126,7 @@ typedef struct TaskState {
 #endif
     uint32_t stack_base;
     int used; /* non zero if used */
+    bool sigsegv_blocked; /* SIGSEGV blocked by guest */
     struct image_info *info;
     struct linux_binprm *bprm;
 
diff --git a/linux-user/signal.c b/linux-user/signal.c
index 9e6a4952a7..e5fb9332e3 100644
--- a/linux-user/signal.c
+++ b/linux-user/signal.c
@@ -204,7 +204,46 @@ void target_to_host_old_sigset(sigset_t *sigset,
  */
 int do_sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
 {
-    return sigprocmask(how, set, oldset);
+    int ret;
+    sigset_t val;
+    sigset_t *temp = NULL;
+    CPUState *cpu = thread_cpu;
+    TaskState *ts = (TaskState *)cpu->opaque;
+    bool segv_was_blocked = ts->sigsegv_blocked;
+
+    if (set) {
+        bool has_sigsegv = sigismember(set, SIGSEGV);
+        val = *set;
+        temp = &val;
+
+        sigdelset(temp, SIGSEGV);
+
+        switch (how) {
+        case SIG_BLOCK:
+            if (has_sigsegv) {
+                ts->sigsegv_blocked = true;
+            }
+            break;
+        case SIG_UNBLOCK:
+            if (has_sigsegv) {
+                ts->sigsegv_blocked = false;
+            }
+            break;
+        case SIG_SETMASK:
+            ts->sigsegv_blocked = has_sigsegv;
+            break;
+        default:
+            g_assert_not_reached();
+        }
+    }
+
+    ret = sigprocmask(how, temp, oldset);
+
+    if (oldset && segv_was_blocked) {
+        sigaddset(oldset, SIGSEGV);
+    }
+
+    return ret;
 }
 
 /* siginfo conversion */
@@ -468,6 +507,19 @@ int queue_signal(CPUArchState *env, int sig, target_siginfo_t *info)
     k = &ts->sigtab[sig - 1];
     queue = gdb_queuesig ();
     handler = sigact_table[sig - 1]._sa_handler;
+
+    if (ts->sigsegv_blocked && sig == TARGET_SIGSEGV) {
+        /* Guest has blocked SIGSEGV but we got one anyway. Assume this
+         * is a forced SIGSEGV (ie one the kernel handles via force_sig_info
+         * because it got a real MMU fault). A blocked SIGSEGV in that
+         * situation is treated as if using the default handler. This is
+         * not correct if some other process has randomly sent us a SIGSEGV
+         * via kill(), but that is not easy to distinguish at this point,
+         * so we assume it doesn't happen.
+         */
+        handler = TARGET_SIG_DFL;
+    }
+
     if (!queue && handler == TARGET_SIG_DFL) {
         if (sig == TARGET_SIGTSTP || sig == TARGET_SIGTTIN || sig == TARGET_SIGTTOU) {
             kill(getpid(),SIGSTOP);
@@ -5726,6 +5778,14 @@ void process_pending_signals(CPUArchState *cpu_env)
         handler = sa->_sa_handler;
     }
 
+    if (ts->sigsegv_blocked && sig == TARGET_SIGSEGV) {
+        /* Guest has blocked SIGSEGV but we got one anyway. Assume this
+         * is a forced SIGSEGV (ie one the kernel handles via force_sig_info
+         * because it got a real MMU fault), and treat as if default handler.
+         */
+        handler = TARGET_SIG_DFL;
+    }
+
     if (handler == TARGET_SIG_DFL) {
         /* default handler : ignore some signal. The other are job control or fatal */
         if (sig == TARGET_SIGTSTP || sig == TARGET_SIGTTIN || sig == TARGET_SIGTTOU) {