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 <stdlib.h> 6 7 #if defined(OS_WIN) 8 #include <dwmapi.h> 9 #include <windows.h> 10 #endif 11 12 #include "base/debug/trace_event.h" 13 #include "base/lazy_instance.h" 14 #include "base/message_loop/message_loop.h" 15 #include "base/metrics/histogram.h" 16 #include "base/rand_util.h" 17 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/threading/platform_thread.h" 20 #include "build/build_config.h" 21 #include "content/child/child_process.h" 22 #include "content/common/content_constants_internal.h" 23 #include "content/common/gpu/gpu_config.h" 24 #include "content/common/gpu/gpu_messages.h" 25 #include "content/common/gpu/media/gpu_video_encode_accelerator.h" 26 #include "content/common/sandbox_linux/sandbox_linux.h" 27 #include "content/gpu/gpu_child_thread.h" 28 #include "content/gpu/gpu_process.h" 29 #include "content/gpu/gpu_watchdog_thread.h" 30 #include "content/public/common/content_client.h" 31 #include "content/public/common/content_switches.h" 32 #include "content/public/common/main_function_params.h" 33 #include "gpu/command_buffer/service/gpu_switches.h" 34 #include "gpu/config/gpu_info_collector.h" 35 #include "gpu/config/gpu_util.h" 36 #include "ui/events/platform/platform_event_source.h" 37 #include "ui/gl/gl_implementation.h" 38 #include "ui/gl/gl_surface.h" 39 #include "ui/gl/gl_switches.h" 40 #include "ui/gl/gpu_switching_manager.h" 41 42 #if defined(OS_WIN) 43 #include "base/win/windows_version.h" 44 #include "base/win/scoped_com_initializer.h" 45 #include "sandbox/win/src/sandbox.h" 46 #endif 47 48 #if defined(USE_X11) 49 #include "ui/base/x/x11_util.h" 50 #endif 51 52 #if defined(OS_LINUX) 53 #include "content/public/common/sandbox_init.h" 54 #endif 55 56 #if defined(OS_MACOSX) 57 #include "base/message_loop/message_pump_mac.h" 58 #include "content/common/sandbox_mac.h" 59 #endif 60 61 #if defined(ADDRESS_SANITIZER) 62 #include <sanitizer/asan_interface.h> 63 #endif 64 65 const int kGpuTimeout = 10000; 66 67 namespace content { 68 69 namespace { 70 71 void GetGpuInfoFromCommandLine(gpu::GPUInfo& gpu_info, 72 const CommandLine& command_line); 73 bool WarmUpSandbox(const CommandLine& command_line); 74 75 #if !defined(OS_MACOSX) 76 bool CollectGraphicsInfo(gpu::GPUInfo& gpu_info); 77 #endif 78 79 #if defined(OS_LINUX) 80 #if !defined(OS_CHROMEOS) 81 bool CanAccessNvidiaDeviceFile(); 82 #endif 83 bool StartSandboxLinux(const gpu::GPUInfo&, GpuWatchdogThread*, bool); 84 #elif defined(OS_WIN) 85 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo*); 86 #endif 87 88 base::LazyInstance<GpuChildThread::DeferredMessages> deferred_messages = 89 LAZY_INSTANCE_INITIALIZER; 90 91 bool GpuProcessLogMessageHandler(int severity, 92 const char* file, int line, 93 size_t message_start, 94 const std::string& str) { 95 std::string header = str.substr(0, message_start); 96 std::string message = str.substr(message_start); 97 deferred_messages.Get().push(new GpuHostMsg_OnLogMessage( 98 severity, header, message)); 99 return false; 100 } 101 102 } // namespace anonymous 103 104 // Main function for starting the Gpu process. 105 int GpuMain(const MainFunctionParams& parameters) { 106 TRACE_EVENT0("gpu", "GpuMain"); 107 base::debug::TraceLog::GetInstance()->SetProcessName("GPU Process"); 108 base::debug::TraceLog::GetInstance()->SetProcessSortIndex( 109 kTraceEventGpuProcessSortIndex); 110 111 const CommandLine& command_line = parameters.command_line; 112 if (command_line.HasSwitch(switches::kGpuStartupDialog)) { 113 ChildProcess::WaitForDebugger("Gpu"); 114 } 115 116 base::Time start_time = base::Time::Now(); 117 118 #if defined(OS_WIN) 119 // Prevent Windows from displaying a modal dialog on failures like not being 120 // able to load a DLL. 121 SetErrorMode( 122 SEM_FAILCRITICALERRORS | 123 SEM_NOGPFAULTERRORBOX | 124 SEM_NOOPENFILEERRORBOX); 125 #elif defined(USE_X11) 126 ui::SetDefaultX11ErrorHandlers(); 127 #endif 128 129 logging::SetLogMessageHandler(GpuProcessLogMessageHandler); 130 131 if (command_line.HasSwitch(switches::kSupportsDualGpus)) { 132 std::string types = command_line.GetSwitchValueASCII( 133 switches::kGpuDriverBugWorkarounds); 134 std::set<int> workarounds; 135 gpu::StringToFeatureSet(types, &workarounds); 136 if (workarounds.count(gpu::FORCE_DISCRETE_GPU) == 1) 137 ui::GpuSwitchingManager::GetInstance()->ForceUseOfDiscreteGpu(); 138 else if (workarounds.count(gpu::FORCE_INTEGRATED_GPU) == 1) 139 ui::GpuSwitchingManager::GetInstance()->ForceUseOfIntegratedGpu(); 140 } 141 142 // Initialization of the OpenGL bindings may fail, in which case we 143 // will need to tear down this process. However, we can not do so 144 // safely until the IPC channel is set up, because the detection of 145 // early return of a child process is implemented using an IPC 146 // channel error. If the IPC channel is not fully set up between the 147 // browser and GPU process, and the GPU process crashes or exits 148 // early, the browser process will never detect it. For this reason 149 // we defer tearing down the GPU process until receiving the 150 // GpuMsg_Initialize message from the browser. 151 bool dead_on_arrival = false; 152 153 #if defined(OS_WIN) 154 base::MessageLoop::Type message_loop_type = base::MessageLoop::TYPE_IO; 155 // Unless we're running on desktop GL, we don't need a UI message 156 // loop, so avoid its use to work around apparent problems with some 157 // third-party software. 158 if (command_line.HasSwitch(switches::kUseGL) && 159 command_line.GetSwitchValueASCII(switches::kUseGL) == 160 gfx::kGLImplementationDesktopName) { 161 message_loop_type = base::MessageLoop::TYPE_UI; 162 } 163 base::MessageLoop main_message_loop(message_loop_type); 164 #elif defined(OS_LINUX) && defined(USE_X11) 165 // We need a UI loop so that we can grab the Expose events. See GLSurfaceGLX 166 // and https://crbug.com/326995. 167 base::MessageLoop main_message_loop(base::MessageLoop::TYPE_UI); 168 scoped_ptr<ui::PlatformEventSource> event_source = 169 ui::PlatformEventSource::CreateDefault(); 170 #elif defined(OS_LINUX) 171 base::MessageLoop main_message_loop(base::MessageLoop::TYPE_DEFAULT); 172 #elif defined(OS_MACOSX) 173 // This is necessary for CoreAnimation layers hosted in the GPU process to be 174 // drawn. See http://crbug.com/312462. 175 scoped_ptr<base::MessagePump> pump(new base::MessagePumpCFRunLoop()); 176 base::MessageLoop main_message_loop(pump.Pass()); 177 #else 178 base::MessageLoop main_message_loop(base::MessageLoop::TYPE_IO); 179 #endif 180 181 base::PlatformThread::SetName("CrGpuMain"); 182 183 // In addition to disabling the watchdog if the command line switch is 184 // present, disable the watchdog on valgrind because the code is expected 185 // to run slowly in that case. 186 bool enable_watchdog = 187 !command_line.HasSwitch(switches::kDisableGpuWatchdog) && 188 !RunningOnValgrind(); 189 190 // Disable the watchdog in debug builds because they tend to only be run by 191 // developers who will not appreciate the watchdog killing the GPU process. 192 #ifndef NDEBUG 193 enable_watchdog = false; 194 #endif 195 196 bool delayed_watchdog_enable = false; 197 198 #if defined(OS_CHROMEOS) 199 // Don't start watchdog immediately, to allow developers to switch to VT2 on 200 // startup. 201 delayed_watchdog_enable = true; 202 #endif 203 204 scoped_refptr<GpuWatchdogThread> watchdog_thread; 205 206 // Start the GPU watchdog only after anything that is expected to be time 207 // consuming has completed, otherwise the process is liable to be aborted. 208 if (enable_watchdog && !delayed_watchdog_enable) { 209 watchdog_thread = new GpuWatchdogThread(kGpuTimeout); 210 base::Thread::Options options; 211 options.timer_slack = base::TIMER_SLACK_MAXIMUM; 212 watchdog_thread->StartWithOptions(options); 213 } 214 215 gpu::GPUInfo gpu_info; 216 // Get vendor_id, device_id, driver_version from browser process through 217 // commandline switches. 218 GetGpuInfoFromCommandLine(gpu_info, command_line); 219 220 base::TimeDelta collect_context_time; 221 base::TimeDelta initialize_one_off_time; 222 223 // Warm up resources that don't need access to GPUInfo. 224 if (WarmUpSandbox(command_line)) { 225 #if defined(OS_LINUX) 226 bool initialized_sandbox = false; 227 bool initialized_gl_context = false; 228 bool should_initialize_gl_context = false; 229 // On Chrome OS ARM Mali, GPU driver userspace creates threads when 230 // initializing a GL context, so start the sandbox early. 231 if (command_line.HasSwitch(switches::kGpuSandboxStartEarly)) { 232 gpu_info.sandboxed = StartSandboxLinux( 233 gpu_info, watchdog_thread.get(), should_initialize_gl_context); 234 initialized_sandbox = true; 235 } 236 #endif // defined(OS_LINUX) 237 238 base::TimeTicks before_initialize_one_off = base::TimeTicks::Now(); 239 240 // Determine if we need to initialize GL here or it has already been done. 241 bool gl_already_initialized = false; 242 #if defined(OS_MACOSX) 243 if (!command_line.HasSwitch(switches::kNoSandbox)) { 244 // On Mac, if the sandbox is enabled, then GLSurface::InitializeOneOff() 245 // is called from the sandbox warmup code before getting here. 246 gl_already_initialized = true; 247 } 248 #endif 249 if (command_line.HasSwitch(switches::kInProcessGPU)) { 250 // With in-process GPU, GLSurface::InitializeOneOff() is called from 251 // GpuChildThread before getting here. 252 gl_already_initialized = true; 253 } 254 255 // Load and initialize the GL implementation and locate the GL entry points. 256 bool gl_initialized = 257 gl_already_initialized 258 ? gfx::GetGLImplementation() != gfx::kGLImplementationNone 259 : gfx::GLSurface::InitializeOneOff(); 260 if (gl_initialized) { 261 // We need to collect GL strings (VENDOR, RENDERER) for blacklisting 262 // purposes. However, on Mac we don't actually use them. As documented in 263 // crbug.com/222934, due to some driver issues, glGetString could take 264 // multiple seconds to finish, which in turn cause the GPU process to 265 // crash. 266 // By skipping the following code on Mac, we don't really lose anything, 267 // because the basic GPU information is passed down from browser process 268 // and we already registered them through SetGpuInfo() above. 269 base::TimeTicks before_collect_context_graphics_info = 270 base::TimeTicks::Now(); 271 #if !defined(OS_MACOSX) 272 if (!CollectGraphicsInfo(gpu_info)) 273 dead_on_arrival = true; 274 275 #if defined(OS_CHROMEOS) || defined(OS_ANDROID) 276 // Recompute gpu driver bug workarounds - this is specifically useful 277 // on systems where vendor_id/device_id aren't available. 278 if (!command_line.HasSwitch(switches::kDisableGpuDriverBugWorkarounds)) { 279 gpu::ApplyGpuDriverBugWorkarounds( 280 gpu_info, const_cast<CommandLine*>(&command_line)); 281 } 282 #endif 283 284 #if defined(OS_LINUX) 285 initialized_gl_context = true; 286 #if !defined(OS_CHROMEOS) 287 if (gpu_info.gpu.vendor_id == 0x10de && // NVIDIA 288 gpu_info.driver_vendor == "NVIDIA" && 289 !CanAccessNvidiaDeviceFile()) 290 dead_on_arrival = true; 291 #endif // !defined(OS_CHROMEOS) 292 #endif // defined(OS_LINUX) 293 #endif // !defined(OS_MACOSX) 294 collect_context_time = 295 base::TimeTicks::Now() - before_collect_context_graphics_info; 296 } else { // gl_initialized 297 VLOG(1) << "gfx::GLSurface::InitializeOneOff failed"; 298 dead_on_arrival = true; 299 } 300 301 initialize_one_off_time = 302 base::TimeTicks::Now() - before_initialize_one_off; 303 304 if (enable_watchdog && delayed_watchdog_enable) { 305 watchdog_thread = new GpuWatchdogThread(kGpuTimeout); 306 base::Thread::Options options; 307 options.timer_slack = base::TIMER_SLACK_MAXIMUM; 308 watchdog_thread->StartWithOptions(options); 309 } 310 311 // OSMesa is expected to run very slowly, so disable the watchdog in that 312 // case. 313 if (enable_watchdog && 314 gfx::GetGLImplementation() == gfx::kGLImplementationOSMesaGL) { 315 watchdog_thread->Stop(); 316 watchdog_thread = NULL; 317 } 318 319 #if defined(OS_LINUX) 320 should_initialize_gl_context = !initialized_gl_context && 321 !dead_on_arrival; 322 323 if (!initialized_sandbox) { 324 gpu_info.sandboxed = StartSandboxLinux(gpu_info, watchdog_thread.get(), 325 should_initialize_gl_context); 326 } 327 #elif defined(OS_WIN) 328 gpu_info.sandboxed = StartSandboxWindows(parameters.sandbox_info); 329 #elif defined(OS_MACOSX) 330 gpu_info.sandboxed = Sandbox::SandboxIsCurrentlyActive(); 331 #endif 332 333 gpu_info.video_encode_accelerator_supported_profiles = 334 content::GpuVideoEncodeAccelerator::GetSupportedProfiles(); 335 } else { 336 dead_on_arrival = true; 337 } 338 339 logging::SetLogMessageHandler(NULL); 340 341 GpuProcess gpu_process; 342 343 // These UMA must be stored after GpuProcess is constructed as it 344 // initializes StatisticsRecorder which tracks the histograms. 345 UMA_HISTOGRAM_TIMES("GPU.CollectContextGraphicsInfo", collect_context_time); 346 UMA_HISTOGRAM_TIMES("GPU.InitializeOneOffTime", initialize_one_off_time); 347 348 GpuChildThread* child_thread = new GpuChildThread(watchdog_thread.get(), 349 dead_on_arrival, 350 gpu_info, 351 deferred_messages.Get()); 352 while (!deferred_messages.Get().empty()) 353 deferred_messages.Get().pop(); 354 355 child_thread->Init(start_time); 356 357 gpu_process.set_main_thread(child_thread); 358 359 if (watchdog_thread.get()) 360 watchdog_thread->AddPowerObserver(); 361 362 { 363 TRACE_EVENT0("gpu", "Run Message Loop"); 364 main_message_loop.Run(); 365 } 366 367 child_thread->StopWatchdog(); 368 369 return 0; 370 } 371 372 namespace { 373 374 void GetGpuInfoFromCommandLine(gpu::GPUInfo& gpu_info, 375 const CommandLine& command_line) { 376 DCHECK(command_line.HasSwitch(switches::kGpuVendorID) && 377 command_line.HasSwitch(switches::kGpuDeviceID) && 378 command_line.HasSwitch(switches::kGpuDriverVersion)); 379 bool success = base::HexStringToUInt( 380 command_line.GetSwitchValueASCII(switches::kGpuVendorID), 381 &gpu_info.gpu.vendor_id); 382 DCHECK(success); 383 success = base::HexStringToUInt( 384 command_line.GetSwitchValueASCII(switches::kGpuDeviceID), 385 &gpu_info.gpu.device_id); 386 DCHECK(success); 387 gpu_info.driver_vendor = 388 command_line.GetSwitchValueASCII(switches::kGpuDriverVendor); 389 gpu_info.driver_version = 390 command_line.GetSwitchValueASCII(switches::kGpuDriverVersion); 391 GetContentClient()->SetGpuInfo(gpu_info); 392 } 393 394 bool WarmUpSandbox(const CommandLine& command_line) { 395 { 396 TRACE_EVENT0("gpu", "Warm up rand"); 397 // Warm up the random subsystem, which needs to be done pre-sandbox on all 398 // platforms. 399 (void) base::RandUint64(); 400 } 401 return true; 402 } 403 404 #if !defined(OS_MACOSX) 405 bool CollectGraphicsInfo(gpu::GPUInfo& gpu_info) { 406 bool res = true; 407 gpu::CollectInfoResult result = gpu::CollectContextGraphicsInfo(&gpu_info); 408 switch (result) { 409 case gpu::kCollectInfoFatalFailure: 410 LOG(ERROR) << "gpu::CollectGraphicsInfo failed (fatal)."; 411 res = false; 412 break; 413 case gpu::kCollectInfoNonFatalFailure: 414 VLOG(1) << "gpu::CollectGraphicsInfo failed (non-fatal)."; 415 break; 416 case gpu::kCollectInfoNone: 417 NOTREACHED(); 418 break; 419 case gpu::kCollectInfoSuccess: 420 break; 421 } 422 GetContentClient()->SetGpuInfo(gpu_info); 423 return res; 424 } 425 #endif 426 427 #if defined(OS_LINUX) 428 #if !defined(OS_CHROMEOS) 429 bool CanAccessNvidiaDeviceFile() { 430 bool res = true; 431 base::ThreadRestrictions::AssertIOAllowed(); 432 if (access("/dev/nvidiactl", R_OK) != 0) { 433 VLOG(1) << "NVIDIA device file /dev/nvidiactl access denied"; 434 res = false; 435 } 436 return res; 437 } 438 #endif 439 440 void CreateDummyGlContext() { 441 scoped_refptr<gfx::GLSurface> surface( 442 gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size())); 443 if (!surface.get()) { 444 VLOG(1) << "gfx::GLSurface::CreateOffscreenGLSurface failed"; 445 return; 446 } 447 448 // On Linux, this is needed to make sure /dev/nvidiactl has 449 // been opened and its descriptor cached. 450 scoped_refptr<gfx::GLContext> context(gfx::GLContext::CreateGLContext( 451 NULL, surface.get(), gfx::PreferDiscreteGpu)); 452 if (!context.get()) { 453 VLOG(1) << "gfx::GLContext::CreateGLContext failed"; 454 return; 455 } 456 457 // Similarly, this is needed for /dev/nvidia0. 458 if (context->MakeCurrent(surface.get())) { 459 context->ReleaseCurrent(surface.get()); 460 } else { 461 VLOG(1) << "gfx::GLContext::MakeCurrent failed"; 462 } 463 } 464 465 void WarmUpSandboxNvidia(const gpu::GPUInfo& gpu_info, 466 bool should_initialize_gl_context) { 467 // We special case Optimus since the vendor_id we see may not be Nvidia. 468 bool uses_nvidia_driver = (gpu_info.gpu.vendor_id == 0x10de && // NVIDIA. 469 gpu_info.driver_vendor == "NVIDIA") || 470 gpu_info.optimus; 471 if (uses_nvidia_driver && should_initialize_gl_context) { 472 // We need this on Nvidia to pre-open /dev/nvidiactl and /dev/nvidia0. 473 CreateDummyGlContext(); 474 } 475 } 476 477 bool StartSandboxLinux(const gpu::GPUInfo& gpu_info, 478 GpuWatchdogThread* watchdog_thread, 479 bool should_initialize_gl_context) { 480 TRACE_EVENT0("gpu", "Initialize sandbox"); 481 482 bool res = false; 483 484 WarmUpSandboxNvidia(gpu_info, should_initialize_gl_context); 485 486 if (watchdog_thread) { 487 // LinuxSandbox needs to be able to ensure that the thread 488 // has really been stopped. 489 LinuxSandbox::StopThread(watchdog_thread); 490 } 491 492 #if defined(ADDRESS_SANITIZER) 493 const std::string sancov_file_name = 494 "gpu." + base::Uint64ToString(base::RandUint64()); 495 LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance(); 496 linux_sandbox->sanitizer_args()->coverage_sandboxed = 1; 497 linux_sandbox->sanitizer_args()->coverage_fd = 498 __sanitizer_maybe_open_cov_file(sancov_file_name.c_str()); 499 linux_sandbox->sanitizer_args()->coverage_max_block_size = 0; 500 #endif 501 502 // LinuxSandbox::InitializeSandbox() must always be called 503 // with only one thread. 504 res = LinuxSandbox::InitializeSandbox(); 505 if (watchdog_thread) { 506 base::Thread::Options options; 507 options.timer_slack = base::TIMER_SLACK_MAXIMUM; 508 watchdog_thread->StartWithOptions(options); 509 } 510 511 return res; 512 } 513 #endif // defined(OS_LINUX) 514 515 #if defined(OS_WIN) 516 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo* sandbox_info) { 517 TRACE_EVENT0("gpu", "Lower token"); 518 519 // For Windows, if the target_services interface is not zero, the process 520 // is sandboxed and we must call LowerToken() before rendering untrusted 521 // content. 522 sandbox::TargetServices* target_services = sandbox_info->target_services; 523 if (target_services) { 524 target_services->LowerToken(); 525 return true; 526 } 527 528 return false; 529 } 530 #endif // defined(OS_WIN) 531 532 } // namespace. 533 534 } // namespace content 535