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
65const int kGpuTimeout = 10000;
66
67namespace content {
68
69namespace {
70
71void GetGpuInfoFromCommandLine(gpu::GPUInfo& gpu_info,
72                               const CommandLine& command_line);
73bool WarmUpSandbox(const CommandLine& command_line);
74
75#if !defined(OS_MACOSX)
76bool CollectGraphicsInfo(gpu::GPUInfo& gpu_info);
77#endif
78
79#if defined(OS_LINUX)
80#if !defined(OS_CHROMEOS)
81bool CanAccessNvidiaDeviceFile();
82#endif
83bool StartSandboxLinux(const gpu::GPUInfo&, GpuWatchdogThread*, bool);
84#elif defined(OS_WIN)
85bool StartSandboxWindows(const sandbox::SandboxInterfaceInfo*);
86#endif
87
88base::LazyInstance<GpuChildThread::DeferredMessages> deferred_messages =
89    LAZY_INSTANCE_INITIALIZER;
90
91bool 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.
105int 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
372namespace {
373
374void 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
394bool 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)
405bool 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)
429bool 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
440void 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
465void 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
477bool 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)
516bool 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