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 <windows.h> 9 #endif 10 11 #include "base/debug/trace_event.h" 12 #include "base/lazy_instance.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/rand_util.h" 15 #include "base/strings/string_number_conversions.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/threading/platform_thread.h" 18 #include "build/build_config.h" 19 #include "content/child/child_process.h" 20 #include "content/common/content_constants_internal.h" 21 #include "content/common/gpu/gpu_config.h" 22 #include "content/common/gpu/gpu_messages.h" 23 #include "content/common/sandbox_linux.h" 24 #include "content/gpu/gpu_child_thread.h" 25 #include "content/gpu/gpu_process.h" 26 #include "content/gpu/gpu_watchdog_thread.h" 27 #include "content/public/common/content_client.h" 28 #include "content/public/common/content_switches.h" 29 #include "content/public/common/main_function_params.h" 30 #include "crypto/hmac.h" 31 #include "gpu/config/gpu_info_collector.h" 32 #include "ui/gl/gl_implementation.h" 33 #include "ui/gl/gl_surface.h" 34 #include "ui/gl/gl_switches.h" 35 #include "ui/gl/gpu_switching_manager.h" 36 37 #if defined(OS_WIN) 38 #include "base/win/scoped_com_initializer.h" 39 #include "content/common/gpu/media/dxva_video_decode_accelerator.h" 40 #include "sandbox/win/src/sandbox.h" 41 #elif defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) && defined(USE_X11) 42 #include "content/common/gpu/media/exynos_video_decode_accelerator.h" 43 #elif defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11) 44 #include "content/common/gpu/media/vaapi_wrapper.h" 45 #endif 46 47 #if defined(USE_X11) 48 #include "ui/base/x/x11_util.h" 49 #endif 50 51 #if defined(OS_LINUX) 52 #include "content/public/common/sandbox_init.h" 53 #endif 54 55 const int kGpuTimeout = 10000; 56 57 namespace content { 58 59 namespace { 60 61 bool WarmUpSandbox(const CommandLine& command_line); 62 #if defined(OS_LINUX) 63 bool StartSandboxLinux(const gpu::GPUInfo&, GpuWatchdogThread*, bool); 64 #elif defined(OS_WIN) 65 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo*); 66 #endif 67 68 base::LazyInstance<GpuChildThread::DeferredMessages> deferred_messages = 69 LAZY_INSTANCE_INITIALIZER; 70 71 bool GpuProcessLogMessageHandler(int severity, 72 const char* file, int line, 73 size_t message_start, 74 const std::string& str) { 75 std::string header = str.substr(0, message_start); 76 std::string message = str.substr(message_start); 77 deferred_messages.Get().push(new GpuHostMsg_OnLogMessage( 78 severity, header, message)); 79 return false; 80 } 81 82 } // namespace anonymous 83 84 // Main function for starting the Gpu process. 85 int GpuMain(const MainFunctionParams& parameters) { 86 TRACE_EVENT0("gpu", "GpuMain"); 87 base::debug::TraceLog::GetInstance()->SetProcessName("GPU Process"); 88 base::debug::TraceLog::GetInstance()->SetProcessSortIndex( 89 kTraceEventGpuProcessSortIndex); 90 91 const CommandLine& command_line = parameters.command_line; 92 if (command_line.HasSwitch(switches::kGpuStartupDialog)) { 93 ChildProcess::WaitForDebugger("Gpu"); 94 } 95 96 base::Time start_time = base::Time::Now(); 97 98 bool in_browser_process = command_line.HasSwitch(switches::kSingleProcess) || 99 command_line.HasSwitch(switches::kInProcessGPU); 100 101 if (!in_browser_process) { 102 #if defined(OS_WIN) 103 // Prevent Windows from displaying a modal dialog on failures like not being 104 // able to load a DLL. 105 SetErrorMode( 106 SEM_FAILCRITICALERRORS | 107 SEM_NOGPFAULTERRORBOX | 108 SEM_NOOPENFILEERRORBOX); 109 #elif defined(USE_X11) 110 ui::SetDefaultX11ErrorHandlers(); 111 #endif 112 113 logging::SetLogMessageHandler(GpuProcessLogMessageHandler); 114 } 115 116 if (command_line.HasSwitch(switches::kSupportsDualGpus) && 117 command_line.HasSwitch(switches::kGpuSwitching)) { 118 std::string option = command_line.GetSwitchValueASCII( 119 switches::kGpuSwitching); 120 if (option == switches::kGpuSwitchingOptionNameForceDiscrete) 121 ui::GpuSwitchingManager::GetInstance()->ForceUseOfDiscreteGpu(); 122 else if (option == switches::kGpuSwitchingOptionNameForceIntegrated) 123 ui::GpuSwitchingManager::GetInstance()->ForceUseOfIntegratedGpu(); 124 } 125 126 // Initialization of the OpenGL bindings may fail, in which case we 127 // will need to tear down this process. However, we can not do so 128 // safely until the IPC channel is set up, because the detection of 129 // early return of a child process is implemented using an IPC 130 // channel error. If the IPC channel is not fully set up between the 131 // browser and GPU process, and the GPU process crashes or exits 132 // early, the browser process will never detect it. For this reason 133 // we defer tearing down the GPU process until receiving the 134 // GpuMsg_Initialize message from the browser. 135 bool dead_on_arrival = false; 136 137 base::MessageLoop::Type message_loop_type = base::MessageLoop::TYPE_IO; 138 #if defined(OS_WIN) 139 // Unless we're running on desktop GL, we don't need a UI message 140 // loop, so avoid its use to work around apparent problems with some 141 // third-party software. 142 if (command_line.HasSwitch(switches::kUseGL) && 143 command_line.GetSwitchValueASCII(switches::kUseGL) == 144 gfx::kGLImplementationDesktopName) { 145 message_loop_type = base::MessageLoop::TYPE_UI; 146 } 147 #elif defined(OS_LINUX) 148 message_loop_type = base::MessageLoop::TYPE_DEFAULT; 149 #endif 150 151 base::MessageLoop main_message_loop(message_loop_type); 152 base::PlatformThread::SetName("CrGpuMain"); 153 154 // In addition to disabling the watchdog if the command line switch is 155 // present, disable the watchdog on valgrind because the code is expected 156 // to run slowly in that case. 157 bool enable_watchdog = 158 !command_line.HasSwitch(switches::kDisableGpuWatchdog) && 159 !RunningOnValgrind(); 160 161 // Disable the watchdog in debug builds because they tend to only be run by 162 // developers who will not appreciate the watchdog killing the GPU process. 163 #ifndef NDEBUG 164 enable_watchdog = false; 165 #endif 166 167 bool delayed_watchdog_enable = false; 168 169 #if defined(OS_CHROMEOS) 170 // Don't start watchdog immediately, to allow developers to switch to VT2 on 171 // startup. 172 delayed_watchdog_enable = true; 173 #endif 174 175 scoped_refptr<GpuWatchdogThread> watchdog_thread; 176 177 // Start the GPU watchdog only after anything that is expected to be time 178 // consuming has completed, otherwise the process is liable to be aborted. 179 if (enable_watchdog && !delayed_watchdog_enable) { 180 watchdog_thread = new GpuWatchdogThread(kGpuTimeout); 181 watchdog_thread->Start(); 182 } 183 184 gpu::GPUInfo gpu_info; 185 // Get vendor_id, device_id, driver_version from browser process through 186 // commandline switches. 187 DCHECK(command_line.HasSwitch(switches::kGpuVendorID) && 188 command_line.HasSwitch(switches::kGpuDeviceID) && 189 command_line.HasSwitch(switches::kGpuDriverVersion)); 190 bool success = base::HexStringToInt( 191 command_line.GetSwitchValueASCII(switches::kGpuVendorID), 192 reinterpret_cast<int*>(&(gpu_info.gpu.vendor_id))); 193 DCHECK(success); 194 success = base::HexStringToInt( 195 command_line.GetSwitchValueASCII(switches::kGpuDeviceID), 196 reinterpret_cast<int*>(&(gpu_info.gpu.device_id))); 197 DCHECK(success); 198 gpu_info.driver_vendor = 199 command_line.GetSwitchValueASCII(switches::kGpuDriverVendor); 200 gpu_info.driver_version = 201 command_line.GetSwitchValueASCII(switches::kGpuDriverVersion); 202 GetContentClient()->SetGpuInfo(gpu_info); 203 204 // Warm up resources that don't need access to GPUInfo. 205 if (WarmUpSandbox(command_line)) { 206 #if defined(OS_LINUX) 207 bool initialized_sandbox = false; 208 bool initialized_gl_context = false; 209 bool should_initialize_gl_context = false; 210 #if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) 211 // On Chrome OS ARM, GPU driver userspace creates threads when initializing 212 // a GL context, so start the sandbox early. 213 gpu_info.sandboxed = StartSandboxLinux(gpu_info, watchdog_thread.get(), 214 should_initialize_gl_context); 215 initialized_sandbox = true; 216 #endif 217 #endif // defined(OS_LINUX) 218 219 // Load and initialize the GL implementation and locate the GL entry points. 220 if (gfx::GLSurface::InitializeOneOff()) { 221 // We need to collect GL strings (VENDOR, RENDERER) for blacklisting 222 // purposes. However, on Mac we don't actually use them. As documented in 223 // crbug.com/222934, due to some driver issues, glGetString could take 224 // multiple seconds to finish, which in turn cause the GPU process to 225 // crash. 226 // By skipping the following code on Mac, we don't really lose anything, 227 // because the basic GPU information is passed down from browser process 228 // and we already registered them through SetGpuInfo() above. 229 #if !defined(OS_MACOSX) 230 if (!gpu::CollectContextGraphicsInfo(&gpu_info)) 231 VLOG(1) << "gpu::CollectGraphicsInfo failed"; 232 GetContentClient()->SetGpuInfo(gpu_info); 233 234 #if defined(OS_LINUX) 235 initialized_gl_context = true; 236 #if !defined(OS_CHROMEOS) 237 if (gpu_info.gpu.vendor_id == 0x10de && // NVIDIA 238 gpu_info.driver_vendor == "NVIDIA") { 239 base::ThreadRestrictions::AssertIOAllowed(); 240 if (access("/dev/nvidiactl", R_OK) != 0) { 241 VLOG(1) << "NVIDIA device file /dev/nvidiactl access denied"; 242 dead_on_arrival = true; 243 } 244 } 245 #endif // !defined(OS_CHROMEOS) 246 #endif // defined(OS_LINUX) 247 #endif // !defined(OS_MACOSX) 248 } else { 249 VLOG(1) << "gfx::GLSurface::InitializeOneOff failed"; 250 dead_on_arrival = true; 251 } 252 253 if (enable_watchdog && delayed_watchdog_enable) { 254 watchdog_thread = new GpuWatchdogThread(kGpuTimeout); 255 watchdog_thread->Start(); 256 } 257 258 // OSMesa is expected to run very slowly, so disable the watchdog in that 259 // case. 260 if (enable_watchdog && 261 gfx::GetGLImplementation() == gfx::kGLImplementationOSMesaGL) { 262 watchdog_thread->Stop(); 263 watchdog_thread = NULL; 264 } 265 266 #if defined(OS_LINUX) 267 should_initialize_gl_context = !initialized_gl_context && 268 !dead_on_arrival; 269 270 if (!initialized_sandbox) { 271 gpu_info.sandboxed = StartSandboxLinux(gpu_info, watchdog_thread.get(), 272 should_initialize_gl_context); 273 } 274 #elif defined(OS_WIN) 275 gpu_info.sandboxed = StartSandboxWindows(parameters.sandbox_info); 276 #endif 277 } else { 278 dead_on_arrival = true; 279 } 280 281 logging::SetLogMessageHandler(NULL); 282 283 GpuProcess gpu_process; 284 285 GpuChildThread* child_thread = new GpuChildThread(watchdog_thread.get(), 286 dead_on_arrival, 287 gpu_info, 288 deferred_messages.Get()); 289 while (!deferred_messages.Get().empty()) 290 deferred_messages.Get().pop(); 291 292 child_thread->Init(start_time); 293 294 gpu_process.set_main_thread(child_thread); 295 296 if (watchdog_thread) 297 watchdog_thread->AddPowerObserver(); 298 299 { 300 TRACE_EVENT0("gpu", "Run Message Loop"); 301 main_message_loop.Run(); 302 } 303 304 child_thread->StopWatchdog(); 305 306 return 0; 307 } 308 309 namespace { 310 311 #if defined(OS_LINUX) 312 void CreateDummyGlContext() { 313 scoped_refptr<gfx::GLSurface> surface( 314 gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1))); 315 if (!surface.get()) { 316 VLOG(1) << "gfx::GLSurface::CreateOffscreenGLSurface failed"; 317 return; 318 } 319 320 // On Linux, this is needed to make sure /dev/nvidiactl has 321 // been opened and its descriptor cached. 322 scoped_refptr<gfx::GLContext> context(gfx::GLContext::CreateGLContext( 323 NULL, surface.get(), gfx::PreferDiscreteGpu)); 324 if (!context.get()) { 325 VLOG(1) << "gfx::GLContext::CreateGLContext failed"; 326 return; 327 } 328 329 // Similarly, this is needed for /dev/nvidia0. 330 if (context->MakeCurrent(surface.get())) { 331 context->ReleaseCurrent(surface.get()); 332 } else { 333 VLOG(1) << "gfx::GLContext::MakeCurrent failed"; 334 } 335 } 336 #endif 337 338 bool WarmUpSandbox(const CommandLine& command_line) { 339 { 340 TRACE_EVENT0("gpu", "Warm up rand"); 341 // Warm up the random subsystem, which needs to be done pre-sandbox on all 342 // platforms. 343 (void) base::RandUint64(); 344 } 345 { 346 TRACE_EVENT0("gpu", "Warm up HMAC"); 347 // Warm up the crypto subsystem, which needs to done pre-sandbox on all 348 // platforms. 349 crypto::HMAC hmac(crypto::HMAC::SHA256); 350 unsigned char key = '\0'; 351 if (!hmac.Init(&key, sizeof(key))) { 352 LOG(ERROR) << "WarmUpSandbox() failed with crypto::HMAC::Init()"; 353 return false; 354 } 355 } 356 357 #if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARMEL) && defined(USE_X11) 358 ExynosVideoDecodeAccelerator::PreSandboxInitialization(); 359 #elif defined(OS_CHROMEOS) && defined(ARCH_CPU_X86_FAMILY) && defined(USE_X11) 360 VaapiWrapper::PreSandboxInitialization(); 361 #endif 362 363 #if defined(OS_WIN) 364 { 365 TRACE_EVENT0("gpu", "Preload setupapi.dll"); 366 // Preload this DLL because the sandbox prevents it from loading. 367 if (LoadLibrary(L"setupapi.dll") == NULL) { 368 LOG(ERROR) << "WarmUpSandbox() failed with loading setupapi.dll"; 369 return false; 370 } 371 } 372 373 if (!command_line.HasSwitch(switches::kDisableAcceleratedVideoDecode)) { 374 TRACE_EVENT0("gpu", "Initialize DXVA"); 375 // Initialize H/W video decoding stuff which fails in the sandbox. 376 DXVAVideoDecodeAccelerator::PreSandboxInitialization(); 377 } 378 #endif 379 return true; 380 } 381 382 #if defined(OS_LINUX) 383 void WarmUpSandboxNvidia(const gpu::GPUInfo& gpu_info, 384 bool should_initialize_gl_context) { 385 // We special case Optimus since the vendor_id we see may not be Nvidia. 386 bool uses_nvidia_driver = (gpu_info.gpu.vendor_id == 0x10de && // NVIDIA. 387 gpu_info.driver_vendor == "NVIDIA") || 388 gpu_info.optimus; 389 if (uses_nvidia_driver && should_initialize_gl_context) { 390 // We need this on Nvidia to pre-open /dev/nvidiactl and /dev/nvidia0. 391 CreateDummyGlContext(); 392 } 393 } 394 395 bool StartSandboxLinux(const gpu::GPUInfo& gpu_info, 396 GpuWatchdogThread* watchdog_thread, 397 bool should_initialize_gl_context) { 398 TRACE_EVENT0("gpu", "Initialize sandbox"); 399 400 bool res = false; 401 402 WarmUpSandboxNvidia(gpu_info, should_initialize_gl_context); 403 404 if (watchdog_thread) 405 watchdog_thread->Stop(); 406 // LinuxSandbox::InitializeSandbox() must always be called 407 // with only one thread. 408 res = LinuxSandbox::InitializeSandbox(); 409 if (watchdog_thread) 410 watchdog_thread->Start(); 411 412 return res; 413 } 414 #endif // defined(OS_LINUX) 415 416 #if defined(OS_WIN) 417 bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo* sandbox_info) { 418 TRACE_EVENT0("gpu", "Lower token"); 419 420 // For Windows, if the target_services interface is not zero, the process 421 // is sandboxed and we must call LowerToken() before rendering untrusted 422 // content. 423 sandbox::TargetServices* target_services = sandbox_info->target_services; 424 if (target_services) { 425 target_services->LowerToken(); 426 return true; 427 } 428 429 return false; 430 } 431 #endif // defined(OS_WIN) 432 433 } // namespace. 434 435 } // namespace content 436