LCOV - code coverage report
Current view: directory - src/trusted/service_runtime/linux - thread_suspension.c (source / functions) Found Hit Coverage
Test: coverage.lcov Lines: 125 107 85.6 %
Date: 2014-07-02 Functions: 0 0 -

       1                 : /*
       2                 :  * Copyright (c) 2012 The Native Client Authors. All rights reserved.
       3                 :  * Use of this source code is governed by a BSD-style license that can be
       4                 :  * found in the LICENSE file.
       5                 :  */
       6                 : #include "native_client/src/trusted/service_runtime/linux/android_compat.h"
       7                 : 
       8                 : #include <errno.h>
       9                 : #include <linux/futex.h>
      10                 : #include <signal.h>
      11                 : #include <sys/syscall.h>
      12                 : 
      13                 : #include "native_client/src/include/concurrency_ops.h"
      14                 : #include "native_client/src/shared/platform/nacl_check.h"
      15                 : #include "native_client/src/shared/platform/nacl_exit.h"
      16                 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
      17                 : #include "native_client/src/trusted/service_runtime/nacl_app_thread.h"
      18                 : #include "native_client/src/trusted/service_runtime/nacl_globals.h"
      19                 : #include "native_client/src/trusted/service_runtime/nacl_tls.h"
      20                 : #include "native_client/src/trusted/service_runtime/sel_ldr.h"
      21                 : #include "native_client/src/trusted/service_runtime/thread_suspension.h"
      22                 : 
      23                 : 
      24                 : struct NaClAppThreadSuspendedRegisters {
      25                 :   struct NaClSignalContext context;
      26                 : };
      27                 : 
      28                 : /*
      29                 :  * If |*addr| still contains |value|, this waits to be woken up by a
      30                 :  * FutexWake(addr,...) call from another thread; otherwise, it returns
      31                 :  * immediately.
      32                 :  *
      33                 :  * Note that if this is interrupted by a signal, the system call will
      34                 :  * get restarted, but it will recheck whether |*addr| still contains
      35                 :  * |value|.
      36                 :  *
      37                 :  * We use the *_PRIVATE variant to use process-local futexes which are
      38                 :  * slightly faster than shared futexes.  (Private futexes are
      39                 :  * well-known but are not covered by the Linux man page for futex(),
      40                 :  * which is very out-of-date.)
      41                 :  */
      42           22271 : static void FutexWait(Atomic32 *addr, Atomic32 value) {
      43           22271 :   if (syscall(SYS_futex, addr, FUTEX_WAIT_PRIVATE, value, 0, 0, 0) != 0) {
      44                 :     /*
      45                 :      * We get EWOULDBLOCK if *addr != value (EAGAIN is a synonym).
      46                 :      * We get EINTR if interrupted by a signal.
      47                 :      */
      48             558 :     if (errno != EINTR && errno != EWOULDBLOCK) {
      49               0 :       NaClLog(LOG_FATAL, "FutexWait: futex() failed with error %d\n", errno);
      50                 :     }
      51                 :   }
      52           22254 : }
      53                 : 
      54                 : /*
      55                 :  * This wakes up threads that are waiting on |addr| using FutexWait().
      56                 :  * |waiters| is the maximum number of threads that will be woken up.
      57                 :  */
      58           22800 : static void FutexWake(Atomic32 *addr, int waiters) {
      59           22800 :   if (syscall(SYS_futex, addr, FUTEX_WAKE_PRIVATE, waiters, 0, 0, 0) < 0) {
      60               0 :     NaClLog(LOG_FATAL, "FutexWake: futex() failed with error %d\n", errno);
      61                 :   }
      62           22794 : }
      63                 : 
      64         5435971 : void NaClAppThreadSetSuspendState(struct NaClAppThread *natp,
      65                 :                                   enum NaClSuspendState old_state,
      66                 :                                   enum NaClSuspendState new_state) {
      67                 :   while (1) {
      68         5435971 :     Atomic32 state = CompareAndSwap(&natp->suspend_state, old_state, new_state);
      69         5435995 :     if (NACL_LIKELY(state == (Atomic32) old_state)) {
      70         5434936 :       break;
      71                 :     }
      72            1059 :     if ((state & NACL_APP_THREAD_SUSPENDING) != 0) {
      73                 :       /* We have been asked to suspend, so wait. */
      74            1059 :       FutexWait(&natp->suspend_state, state);
      75                 :     } else {
      76               0 :       NaClLog(LOG_FATAL, "NaClAppThreadSetSuspendState: Unexpected state: %i\n",
      77                 :               state);
      78                 :     }
      79            1059 :   }
      80         5434936 : }
      81                 : 
      82           10614 : static void HandleSuspendSignal(struct NaClSignalContext *regs) {
      83           10614 :   struct NaClAppThread *natp = NaClTlsGetCurrentThread();
      84           10614 :   struct NaClSignalContext *suspended_registers =
      85           10614 :       &natp->suspended_registers->context;
      86                 : 
      87                 :   /* Sanity check. */
      88           10614 :   if (natp->suspend_state != (NACL_APP_THREAD_UNTRUSTED |
      89                 :                               NACL_APP_THREAD_SUSPENDING)) {
      90               0 :     NaClSignalErrorMessage("HandleSuspendSignal: "
      91                 :                            "Unexpected suspend_state\n");
      92               0 :     NaClAbort();
      93                 :   }
      94                 : 
      95           10614 :   if (suspended_registers != NULL) {
      96           10490 :     *suspended_registers = *regs;
      97                 :     /*
      98                 :      * Ensure that the change to natp->suspend_state does not become
      99                 :      * visible before the change to natp->suspended_registers.
     100                 :      */
     101           10490 :     NaClWriteMemoryBarrier();
     102                 :   }
     103                 : 
     104                 :   /*
     105                 :    * Indicate that we have suspended by setting
     106                 :    * NACL_APP_THREAD_SUSPENDED.  We should not need an atomic
     107                 :    * operation for this since the calling thread will not be trying to
     108                 :    * change suspend_state.
     109                 :    */
     110           10614 :   natp->suspend_state |= NACL_APP_THREAD_SUSPENDED;
     111           10614 :   FutexWake(&natp->suspend_state, 1);
     112                 : 
     113                 :   /* Wait until we are asked to resume. */
     114                 :   while (1) {
     115           21197 :     Atomic32 state = natp->suspend_state;
     116           21197 :     if ((state & NACL_APP_THREAD_SUSPENDED) != 0) {
     117           10600 :       FutexWait(&natp->suspend_state, state);
     118           10583 :       continue;  /* Retry */
     119                 :     }
     120           10597 :     break;
     121           10583 :   }
     122                 :   /*
     123                 :    * Apply register modifications.  We need to use a snapshot of
     124                 :    * natp->suspended_registers because, since we were asked to resume,
     125                 :    * we could have been asked to suspend again, and
     126                 :    * suspended_registers could have been allocated in the new
     127                 :    * suspension request but not in the original suspension request.
     128                 :    */
     129           10597 :   if (suspended_registers != NULL) {
     130           10473 :     *regs = *suspended_registers;
     131                 :   }
     132           10597 : }
     133                 : 
     134              91 : static void FireDebugStubEvent(int pipe_fd) {
     135              91 :   char buf = 0;
     136              91 :   if (write(pipe_fd, &buf, sizeof(buf)) != sizeof(buf)) {
     137               0 :     NaClSignalErrorMessage("FireDebugStubEvent: Can't send debug stub event\n");
     138               0 :     NaClAbort();
     139                 :   }
     140              91 : }
     141                 : 
     142              91 : static void HandleUntrustedFault(int signal,
     143                 :                                  struct NaClSignalContext *regs,
     144                 :                                  struct NaClAppThread *natp) {
     145                 :   /* Sanity check. */
     146              91 :   if ((natp->suspend_state & NACL_APP_THREAD_UNTRUSTED) == 0) {
     147               0 :     NaClSignalErrorMessage("HandleUntrustedFault: Unexpected suspend_state\n");
     148               0 :     NaClAbort();
     149                 :   }
     150                 : 
     151                 :   /* Notify the debug stub by marking this thread as faulted. */
     152              91 :   natp->fault_signal = signal;
     153              91 :   AtomicIncrement(&natp->nap->faulted_thread_count, 1);
     154              91 :   FireDebugStubEvent(natp->nap->faulted_thread_fd_write);
     155                 : 
     156                 :   /*
     157                 :    * We now expect the debug stub to suspend this thread via the
     158                 :    * thread suspension API.  This will allow the debug stub to get the
     159                 :    * register state at the point the fault happened.  The debug stub
     160                 :    * will be able to modify the register state before unblocking the
     161                 :    * thread using NaClAppThreadUnblockIfFaulted().
     162                 :    */
     163                 :   do {
     164                 :     int new_signal;
     165                 :     sigset_t sigset;
     166              94 :     sigemptyset(&sigset);
     167              94 :     sigaddset(&sigset, NACL_THREAD_SUSPEND_SIGNAL);
     168              94 :     if (sigwait(&sigset, &new_signal) != 0) {
     169               0 :       NaClSignalErrorMessage("HandleUntrustedFault: sigwait() failed\n");
     170               0 :       NaClAbort();
     171                 :     }
     172              94 :     if (new_signal != NACL_THREAD_SUSPEND_SIGNAL) {
     173               0 :       NaClSignalErrorMessage("HandleUntrustedFault: "
     174                 :                              "sigwait() returned unexpected result\n");
     175               0 :       NaClAbort();
     176                 :     }
     177              94 :     HandleSuspendSignal(regs);
     178              78 :   } while (natp->fault_signal != 0);
     179              75 : }
     180                 : 
     181          140375 : int NaClThreadSuspensionSignalHandler(int signal,
     182                 :                                       struct NaClSignalContext *regs,
     183                 :                                       int is_untrusted,
     184                 :                                       struct NaClAppThread *natp) {
     185                 :   /*
     186                 :    * We handle NACL_THREAD_SUSPEND_SIGNAL even if is_untrusted is
     187                 :    * false, because the thread suspension signal can occur in trusted
     188                 :    * code while switching from or to untrusted code while
     189                 :    * suspend_state contains NACL_APP_THREAD_UNTRUSTED.
     190                 :    */
     191          140375 :   if (signal == NACL_THREAD_SUSPEND_SIGNAL) {
     192           10520 :     HandleSuspendSignal(regs);
     193           10519 :     return 1;
     194                 :   }
     195          129855 :   if (is_untrusted && natp->nap->enable_faulted_thread_queue) {
     196              91 :     HandleUntrustedFault(signal, regs, natp);
     197              75 :     return 1;
     198                 :   }
     199                 :   /* We did not handle this signal. */
     200          129764 :   return 0;
     201                 : }
     202                 : 
     203                 : /* Wait for the thread to indicate that it has suspended. */
     204           10614 : static void WaitForUntrustedThreadToSuspend(struct NaClAppThread *natp) {
     205           10614 :   const Atomic32 kBaseState = (NACL_APP_THREAD_UNTRUSTED |
     206                 :                                NACL_APP_THREAD_SUSPENDING);
     207                 :   while (1) {
     208           21226 :     Atomic32 state = natp->suspend_state;
     209           21226 :     if (state == kBaseState) {
     210           10612 :       FutexWait(&natp->suspend_state, state);
     211           10612 :       continue;  /* Retry */
     212                 :     }
     213           10614 :     if (state != (kBaseState | NACL_APP_THREAD_SUSPENDED)) {
     214               0 :       NaClLog(LOG_FATAL, "WaitForUntrustedThreadToSuspend: "
     215                 :               "Unexpected state: %d\n", state);
     216                 :     }
     217           10614 :     break;
     218           10612 :   }
     219           10614 : }
     220                 : 
     221           12567 : void NaClUntrustedThreadSuspend(struct NaClAppThread *natp,
     222                 :                                 int save_registers) {
     223                 :   Atomic32 old_state;
     224                 :   Atomic32 suspending_state;
     225                 : 
     226                 :   /*
     227                 :    * We do not want the thread to enter a NaCl syscall and start
     228                 :    * taking locks when pthread_kill() takes effect, so we ask the
     229                 :    * thread to suspend even if it is currently running untrusted code.
     230                 :    */
     231                 :   while (1) {
     232           12567 :     old_state = natp->suspend_state;
     233           12567 :     DCHECK((old_state & NACL_APP_THREAD_SUSPENDING) == 0);
     234           12567 :     suspending_state = old_state | NACL_APP_THREAD_SUSPENDING;
     235           12567 :     if (CompareAndSwap(&natp->suspend_state, old_state, suspending_state)
     236                 :         != old_state) {
     237             362 :       continue;  /* Retry */
     238                 :     }
     239           12205 :     break;
     240             362 :   }
     241                 :   /*
     242                 :    * Once the thread has NACL_APP_THREAD_SUSPENDING set, it may not
     243                 :    * change state itself, so there should be no race condition in this
     244                 :    * check.
     245                 :    */
     246           12205 :   DCHECK(natp->suspend_state == suspending_state);
     247                 : 
     248           12205 :   if (old_state == NACL_APP_THREAD_UNTRUSTED) {
     249                 :     /*
     250                 :      * Allocate register state struct if needed.  This is race-free
     251                 :      * when we are called by NaClUntrustedThreadsSuspendAll(), since
     252                 :      * that claims nap->threads_mu.
     253                 :      */
     254           10614 :     if (save_registers && natp->suspended_registers == NULL) {
     255              27 :       natp->suspended_registers = malloc(sizeof(*natp->suspended_registers));
     256              27 :       if (natp->suspended_registers == NULL) {
     257               0 :         NaClLog(LOG_FATAL, "NaClUntrustedThreadSuspend: malloc() failed\n");
     258                 :       }
     259                 :     }
     260           10614 :     CHECK(natp->host_thread_is_defined);
     261           10614 :     if (pthread_kill(natp->host_thread.tid, NACL_THREAD_SUSPEND_SIGNAL) != 0) {
     262               0 :       NaClLog(LOG_FATAL, "NaClUntrustedThreadSuspend: "
     263                 :               "pthread_kill() call failed\n");
     264                 :     }
     265           10614 :     WaitForUntrustedThreadToSuspend(natp);
     266                 :   }
     267           12205 : }
     268                 : 
     269           12186 : void NaClUntrustedThreadResume(struct NaClAppThread *natp) {
     270                 :   Atomic32 old_state;
     271                 :   Atomic32 new_state;
     272                 :   while (1) {
     273           12186 :     old_state = natp->suspend_state;
     274           12186 :     new_state = old_state & ~(NACL_APP_THREAD_SUSPENDING |
     275                 :                               NACL_APP_THREAD_SUSPENDED);
     276           12186 :     DCHECK((old_state & NACL_APP_THREAD_SUSPENDING) != 0);
     277           12186 :     if (CompareAndSwap(&natp->suspend_state, old_state, new_state)
     278                 :         != old_state) {
     279               0 :       continue;  /* Retry */
     280                 :     }
     281           12186 :     break;
     282               0 :   }
     283                 : 
     284                 :   /*
     285                 :    * TODO(mseaborn): A refinement would be to wake up the thread only
     286                 :    * if it actually suspended during the context switch.
     287                 :    */
     288           12186 :   FutexWake(&natp->suspend_state, 1);
     289           12186 : }
     290                 : 
     291           20962 : void NaClAppThreadGetSuspendedRegistersInternal(
     292                 :     struct NaClAppThread *natp, struct NaClSignalContext *regs) {
     293           20962 :   *regs = natp->suspended_registers->context;
     294           20962 : }
     295                 : 
     296              77 : void NaClAppThreadSetSuspendedRegistersInternal(
     297                 :     struct NaClAppThread *natp, const struct NaClSignalContext *regs) {
     298              77 :   natp->suspended_registers->context = *regs;
     299              77 : }
     300                 : 
     301              92 : int NaClAppThreadUnblockIfFaulted(struct NaClAppThread *natp, int *signal) {
     302                 :   /* This function may only be called on a thread that is suspended. */
     303              92 :   DCHECK(natp->suspend_state == (NACL_APP_THREAD_UNTRUSTED |
     304                 :                                  NACL_APP_THREAD_SUSPENDING |
     305                 :                                  NACL_APP_THREAD_SUSPENDED) ||
     306                 :          natp->suspend_state == (NACL_APP_THREAD_TRUSTED |
     307                 :                                  NACL_APP_THREAD_SUSPENDING));
     308                 : 
     309              92 :   if (natp->fault_signal == 0) {
     310               1 :     return 0;
     311                 :   }
     312              91 :   *signal = natp->fault_signal;
     313              91 :   natp->fault_signal = 0;
     314              91 :   AtomicIncrement(&natp->nap->faulted_thread_count, -1);
     315              91 :   return 1;
     316                 : }

Generated by: LCOV version 1.7