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