Home | History | Annotate | Download | only in plugin
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ppapi/native_client/src/trusted/plugin/plugin.h"
      6 
      7 #include <sys/stat.h>
      8 #include <sys/types.h>
      9 
     10 #include <string>
     11 
     12 #include "native_client/src/include/nacl_base.h"
     13 #include "native_client/src/include/nacl_macros.h"
     14 #include "native_client/src/include/nacl_scoped_ptr.h"
     15 #include "native_client/src/include/portability.h"
     16 #include "native_client/src/include/portability_io.h"
     17 #include "native_client/src/shared/platform/nacl_check.h"
     18 #include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
     19 
     20 #include "ppapi/c/pp_errors.h"
     21 #include "ppapi/c/private/ppb_nacl_private.h"
     22 #include "ppapi/cpp/module.h"
     23 
     24 #include "ppapi/native_client/src/trusted/plugin/nacl_subprocess.h"
     25 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
     26 #include "ppapi/native_client/src/trusted/plugin/service_runtime.h"
     27 #include "ppapi/native_client/src/trusted/plugin/utility.h"
     28 
     29 namespace plugin {
     30 
     31 namespace {
     32 
     33 // Up to 20 seconds
     34 const int64_t kTimeSmallMin = 1;         // in ms
     35 const int64_t kTimeSmallMax = 20000;     // in ms
     36 const uint32_t kTimeSmallBuckets = 100;
     37 
     38 }  // namespace
     39 
     40 void Plugin::ShutDownSubprocesses() {
     41   PLUGIN_PRINTF(("Plugin::ShutDownSubprocesses (this=%p)\n",
     42                  static_cast<void*>(this)));
     43 
     44   // Shut down service runtime. This must be done before all other calls so
     45   // they don't block forever when waiting for the upcall thread to exit.
     46   main_subprocess_.Shutdown();
     47 
     48   PLUGIN_PRINTF(("Plugin::ShutDownSubprocess (this=%p, return)\n",
     49                  static_cast<void*>(this)));
     50 }
     51 
     52 void Plugin::HistogramTimeSmall(const std::string& name,
     53                                 int64_t ms) {
     54   if (ms < 0) return;
     55   uma_interface_.HistogramCustomTimes(name,
     56                                       ms,
     57                                       kTimeSmallMin, kTimeSmallMax,
     58                                       kTimeSmallBuckets);
     59 }
     60 
     61 bool Plugin::LoadHelperNaClModuleInternal(NaClSubprocess* subprocess,
     62                                           const SelLdrStartParams& params) {
     63   CHECK(!pp::Module::Get()->core()->IsMainThread());
     64   ServiceRuntime* service_runtime =
     65       new ServiceRuntime(this,
     66                          pp_instance(),
     67                          false,  // No main_service_runtime.
     68                          false,  // No non-SFI mode (i.e. in SFI-mode).
     69                          pp::BlockUntilComplete());
     70   subprocess->set_service_runtime(service_runtime);
     71 
     72   // Now start the SelLdr instance.  This must be created on the main thread.
     73   bool service_runtime_started = false;
     74   pp::CompletionCallback sel_ldr_callback =
     75       callback_factory_.NewCallback(&Plugin::SignalStartSelLdrDone,
     76                                     &service_runtime_started,
     77                                     service_runtime);
     78   pp::CompletionCallback callback =
     79       callback_factory_.NewCallback(&Plugin::StartSelLdrOnMainThread,
     80                                     service_runtime, params,
     81                                     sel_ldr_callback);
     82   pp::Module::Get()->core()->CallOnMainThread(0, callback, 0);
     83   if (!service_runtime->WaitForSelLdrStart()) {
     84     PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule "
     85                    "WaitForSelLdrStart timed out!\n"));
     86     return false;
     87   }
     88   PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule (service_runtime_started=%d)\n",
     89                  service_runtime_started));
     90   if (!service_runtime_started)
     91     return false;
     92 
     93   // Now actually start the nexe.
     94   //
     95   // We can't use pp::BlockUntilComplete() inside an in-process plugin, so we
     96   // have to roll our own blocking logic, similar to WaitForSelLdrStart()
     97   // above, except without timeout logic.
     98   pp::Module::Get()->core()->CallOnMainThread(
     99       0,
    100       callback_factory_.NewCallback(&Plugin::StartNexe, service_runtime));
    101   return service_runtime->WaitForNexeStart();
    102 }
    103 
    104 void Plugin::StartSelLdrOnMainThread(int32_t pp_error,
    105                                      ServiceRuntime* service_runtime,
    106                                      const SelLdrStartParams& params,
    107                                      pp::CompletionCallback callback) {
    108   CHECK(pp_error == PP_OK);
    109   service_runtime->StartSelLdr(params, callback);
    110 }
    111 
    112 void Plugin::SignalStartSelLdrDone(int32_t pp_error,
    113                                    bool* started,
    114                                    ServiceRuntime* service_runtime) {
    115   *started = (pp_error == PP_OK);
    116   service_runtime->SignalStartSelLdrDone();
    117 }
    118 
    119 void Plugin::LoadNaClModule(PP_NaClFileInfo file_info,
    120                             bool uses_nonsfi_mode,
    121                             bool enable_dyncode_syscalls,
    122                             bool enable_exception_handling,
    123                             bool enable_crash_throttling,
    124                             const pp::CompletionCallback& init_done_cb) {
    125   CHECK(pp::Module::Get()->core()->IsMainThread());
    126   // Before forking a new sel_ldr process, ensure that we do not leak
    127   // the ServiceRuntime object for an existing subprocess, and that any
    128   // associated listener threads do not go unjoined because if they
    129   // outlive the Plugin object, they will not be memory safe.
    130   ShutDownSubprocesses();
    131   pp::Var manifest_base_url =
    132       pp::Var(pp::PASS_REF, nacl_interface_->GetManifestBaseURL(pp_instance()));
    133   std::string manifest_base_url_str = manifest_base_url.AsString();
    134 
    135   SelLdrStartParams params(manifest_base_url_str,
    136                            file_info,
    137                            true /* uses_irt */,
    138                            true /* uses_ppapi */,
    139                            enable_dyncode_syscalls,
    140                            enable_exception_handling,
    141                            enable_crash_throttling);
    142   ErrorInfo error_info;
    143   ServiceRuntime* service_runtime = new ServiceRuntime(
    144       this, pp_instance(), true, uses_nonsfi_mode, init_done_cb);
    145   main_subprocess_.set_service_runtime(service_runtime);
    146   if (NULL == service_runtime) {
    147     error_info.SetReport(
    148         PP_NACL_ERROR_SEL_LDR_INIT,
    149         "sel_ldr init failure " + main_subprocess_.description());
    150     ReportLoadError(error_info);
    151     return;
    152   }
    153 
    154   // We don't take any action once nexe loading has completed, so pass an empty
    155   // callback here for |callback|.
    156   pp::CompletionCallback callback = callback_factory_.NewCallback(
    157       &Plugin::StartNexe, service_runtime);
    158   StartSelLdrOnMainThread(
    159       static_cast<int32_t>(PP_OK), service_runtime, params, callback);
    160 }
    161 
    162 void Plugin::StartNexe(int32_t pp_error, ServiceRuntime* service_runtime) {
    163   CHECK(pp::Module::Get()->core()->IsMainThread());
    164   if (pp_error != PP_OK)
    165     return;
    166   service_runtime->StartNexe();
    167 }
    168 
    169 bool Plugin::LoadNaClModuleContinuationIntern() {
    170   ErrorInfo error_info;
    171   if (!uses_nonsfi_mode_) {
    172     if (!main_subprocess_.StartSrpcServices()) {
    173       // The NaCl process probably crashed. On Linux, a crash causes this
    174       // error, while on other platforms, the error is detected below, when we
    175       // attempt to start the proxy. Report a module initialization error here,
    176       // to make it less confusing for developers.
    177       NaClLog(LOG_ERROR, "LoadNaClModuleContinuationIntern: "
    178               "StartSrpcServices failed\n");
    179       error_info.SetReport(PP_NACL_ERROR_START_PROXY_MODULE,
    180                            "could not initialize module.");
    181       ReportLoadError(error_info);
    182       return false;
    183     }
    184   }
    185 
    186   return PP_ToBool(nacl_interface_->StartPpapiProxy(pp_instance()));
    187 }
    188 
    189 NaClSubprocess* Plugin::LoadHelperNaClModule(const std::string& helper_url,
    190                                              PP_NaClFileInfo file_info,
    191                                              ErrorInfo* error_info) {
    192   nacl::scoped_ptr<NaClSubprocess> nacl_subprocess(
    193       new NaClSubprocess("helper module", NULL, NULL));
    194   if (NULL == nacl_subprocess.get()) {
    195     error_info->SetReport(PP_NACL_ERROR_SEL_LDR_INIT,
    196                           "unable to allocate helper subprocess.");
    197     return NULL;
    198   }
    199 
    200   // Do not report UMA stats for translator-related nexes.
    201   // TODO(sehr): define new UMA stats for translator related nexe events.
    202   // NOTE: The PNaCl translator nexes are not built to use the IRT.  This is
    203   // done to save on address space and swap space.
    204   SelLdrStartParams params(helper_url,
    205                            file_info,
    206                            false /* uses_irt */,
    207                            false /* uses_ppapi */,
    208                            false /* enable_dyncode_syscalls */,
    209                            false /* enable_exception_handling */,
    210                            true /* enable_crash_throttling */);
    211 
    212   // Helper NaCl modules always use the PNaCl manifest, as there is no
    213   // corresponding NMF.
    214   if (!LoadHelperNaClModuleInternal(nacl_subprocess.get(), params))
    215     return NULL;
    216 
    217   // We need not wait for the init_done callback.  We can block
    218   // here in StartSrpcServices, since helper NaCl modules
    219   // are spawned from a private thread.
    220   //
    221   // TODO(bsy): if helper module crashes, we should abort.
    222   // crash_cb is not used here, so we are relying on crashes
    223   // being detected in StartSrpcServices or later.
    224   //
    225   // NB: More refactoring might be needed, however, if helper
    226   // NaCl modules have their own manifest.  Currently the
    227   // manifest is a per-plugin-instance object, not a per
    228   // NaClSubprocess object.
    229   if (!nacl_subprocess->StartSrpcServices()) {
    230     error_info->SetReport(PP_NACL_ERROR_SRPC_CONNECTION_FAIL,
    231                           "SRPC connection failure for " +
    232                           nacl_subprocess->description());
    233     return NULL;
    234   }
    235 
    236   PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule (%s, %s)\n",
    237                  helper_url.c_str(),
    238                  nacl_subprocess.get()->detailed_description().c_str()));
    239 
    240   return nacl_subprocess.release();
    241 }
    242 
    243 // All failures of this function will show up as "Missing Plugin-in", so
    244 // there is no need to log to JS console that there was an initialization
    245 // failure. Note that module loading functions will log their own errors.
    246 bool Plugin::Init(uint32_t argc, const char* argn[], const char* argv[]) {
    247   nacl_interface_->InitializePlugin(pp_instance(), argc, argn, argv);
    248   wrapper_factory_ = new nacl::DescWrapperFactory();
    249   pp::CompletionCallback open_cb =
    250       callback_factory_.NewCallback(&Plugin::NaClManifestFileDidOpen);
    251   nacl_interface_->RequestNaClManifest(pp_instance(),
    252                                        open_cb.pp_completion_callback());
    253   return true;
    254 }
    255 
    256 Plugin::Plugin(PP_Instance pp_instance)
    257     : pp::Instance(pp_instance),
    258       main_subprocess_("main subprocess", NULL, NULL),
    259       uses_nonsfi_mode_(false),
    260       wrapper_factory_(NULL),
    261       nacl_interface_(NULL),
    262       uma_interface_(this) {
    263   callback_factory_.Initialize(this);
    264   nacl_interface_ = GetNaClInterface();
    265   CHECK(nacl_interface_ != NULL);
    266 
    267   // Notify PPB_NaCl_Private that the instance is created before altering any
    268   // state that it tracks.
    269   nacl_interface_->InstanceCreated(pp_instance);
    270   nexe_file_info_ = kInvalidNaClFileInfo;
    271 }
    272 
    273 Plugin::~Plugin() {
    274   int64_t shutdown_start = NaClGetTimeOfDayMicroseconds();
    275 
    276   // Destroy the coordinator while the rest of the data is still there
    277   pnacl_coordinator_.reset(NULL);
    278 
    279   nacl_interface_->InstanceDestroyed(pp_instance());
    280 
    281   // ShutDownSubprocesses shuts down the main subprocess, which shuts
    282   // down the main ServiceRuntime object, which kills the subprocess.
    283   // As a side effect of the subprocess being killed, the reverse
    284   // services thread(s) will get EOF on the reverse channel(s), and
    285   // the thread(s) will exit.  In ServiceRuntime::Shutdown, we invoke
    286   // ReverseService::WaitForServiceThreadsToExit(), so that there will
    287   // not be an extent thread(s) hanging around.  This means that the
    288   // ~Plugin will block until this happens.  This is a requirement,
    289   // since the renderer should be free to unload the plugin code, and
    290   // we cannot have threads running code that gets unloaded before
    291   // they exit.
    292   //
    293   // By waiting for the threads here, we also ensure that the Plugin
    294   // object and the subprocess and ServiceRuntime objects is not
    295   // (fully) destroyed while the threads are running, so resources
    296   // that are destroyed after ShutDownSubprocesses (below) are
    297   // guaranteed to be live and valid for access from the service
    298   // threads.
    299   //
    300   // The main_subprocess object, which wraps the main service_runtime
    301   // object, is dtor'd implicitly after the explicit code below runs,
    302   // so the main service runtime object will not have been dtor'd,
    303   // though the Shutdown method may have been called, during the
    304   // lifetime of the service threads.
    305   ShutDownSubprocesses();
    306 
    307   delete wrapper_factory_;
    308 
    309   HistogramTimeSmall(
    310       "NaCl.Perf.ShutdownTime.Total",
    311       (NaClGetTimeOfDayMicroseconds() - shutdown_start)
    312           / NACL_MICROS_PER_MILLI);
    313 }
    314 
    315 bool Plugin::HandleDocumentLoad(const pp::URLLoader& url_loader) {
    316   // We don't know if the plugin will handle the document load, but return
    317   // true in order to give it a chance to respond once the proxy is started.
    318   return true;
    319 }
    320 
    321 void Plugin::NexeFileDidOpen(int32_t pp_error) {
    322   if (pp_error != PP_OK)
    323     return;
    324   LoadNaClModule(
    325       nexe_file_info_,
    326       uses_nonsfi_mode_,
    327       true, /* enable_dyncode_syscalls */
    328       true, /* enable_exception_handling */
    329       false, /* enable_crash_throttling */
    330       callback_factory_.NewCallback(&Plugin::NexeFileDidOpenContinuation));
    331 }
    332 
    333 void Plugin::NexeFileDidOpenContinuation(int32_t pp_error) {
    334   UNREFERENCED_PARAMETER(pp_error);
    335   NaClLog(4, "Entered NexeFileDidOpenContinuation\n");
    336   if (LoadNaClModuleContinuationIntern()) {
    337     NaClLog(4, "NexeFileDidOpenContinuation: success;"
    338             " setting histograms\n");
    339     int64_t nexe_size = nacl_interface_->GetNexeSize(pp_instance());
    340     nacl_interface_->ReportLoadSuccess(
    341         pp_instance(), nexe_size, nexe_size);
    342   } else {
    343     NaClLog(4, "NexeFileDidOpenContinuation: failed.");
    344   }
    345   NaClLog(4, "Leaving NexeFileDidOpenContinuation\n");
    346 }
    347 
    348 void Plugin::BitcodeDidTranslate(int32_t pp_error) {
    349   PLUGIN_PRINTF(("Plugin::BitcodeDidTranslate (pp_error=%" NACL_PRId32 ")\n",
    350                  pp_error));
    351   if (pp_error != PP_OK) {
    352     // Error should have been reported by pnacl. Just return.
    353     return;
    354   }
    355 
    356   // Inform JavaScript that we successfully translated the bitcode to a nexe.
    357   PP_FileHandle handle = pnacl_coordinator_->TakeTranslatedFileHandle();
    358 
    359   PP_NaClFileInfo info;
    360   info.handle = handle;
    361   info.token_lo = 0;
    362   info.token_hi = 0;
    363   LoadNaClModule(
    364       info,
    365       false, /* uses_nonsfi_mode */
    366       false, /* enable_dyncode_syscalls */
    367       false, /* enable_exception_handling */
    368       true, /* enable_crash_throttling */
    369       callback_factory_.NewCallback(&Plugin::BitcodeDidTranslateContinuation));
    370 }
    371 
    372 void Plugin::BitcodeDidTranslateContinuation(int32_t pp_error) {
    373   NaClLog(4, "Entered BitcodeDidTranslateContinuation\n");
    374   UNREFERENCED_PARAMETER(pp_error);
    375   if (LoadNaClModuleContinuationIntern()) {
    376     int64_t loaded;
    377     int64_t total;
    378     // TODO(teravest): Tighten this up so we can get rid of
    379     // GetCurrentProgress(). loaded should always equal total.
    380     pnacl_coordinator_->GetCurrentProgress(&loaded, &total);
    381     nacl_interface_->ReportLoadSuccess(pp_instance(), loaded, total);
    382   }
    383 }
    384 
    385 void Plugin::NaClManifestFileDidOpen(int32_t pp_error) {
    386   PLUGIN_PRINTF(("Plugin::NaClManifestFileDidOpen (pp_error=%"
    387                  NACL_PRId32 ")\n", pp_error));
    388   if (pp_error != PP_OK)
    389     return;
    390 
    391   PP_Var pp_program_url;
    392   PP_PNaClOptions pnacl_options = {PP_FALSE, PP_FALSE, 2};
    393   PP_Bool uses_nonsfi_mode;
    394   if (nacl_interface_->GetManifestProgramURL(
    395           pp_instance(), &pp_program_url, &pnacl_options, &uses_nonsfi_mode)) {
    396     std::string program_url = pp::Var(pp::PASS_REF, pp_program_url).AsString();
    397     // TODO(teravest): Make ProcessNaClManifest take responsibility for more of
    398     // this function.
    399     nacl_interface_->ProcessNaClManifest(pp_instance(), program_url.c_str());
    400     uses_nonsfi_mode_ = PP_ToBool(uses_nonsfi_mode);
    401     if (pnacl_options.translate) {
    402       pp::CompletionCallback translate_callback =
    403           callback_factory_.NewCallback(&Plugin::BitcodeDidTranslate);
    404       pnacl_coordinator_.reset(
    405           PnaclCoordinator::BitcodeToNative(this,
    406                                             program_url,
    407                                             pnacl_options,
    408                                             translate_callback));
    409       return;
    410     } else {
    411       pp::CompletionCallback open_callback =
    412           callback_factory_.NewCallback(&Plugin::NexeFileDidOpen);
    413       // Will always call the callback on success or failure.
    414       nacl_interface_->DownloadNexe(pp_instance(),
    415                                     program_url.c_str(),
    416                                     &nexe_file_info_,
    417                                     open_callback.pp_completion_callback());
    418       return;
    419     }
    420   }
    421 }
    422 
    423 void Plugin::ReportLoadError(const ErrorInfo& error_info) {
    424   nacl_interface_->ReportLoadError(pp_instance(),
    425                                    error_info.error_code(),
    426                                    error_info.message().c_str());
    427 }
    428 
    429 }  // namespace plugin
    430