# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import distutils
import glob
import heapq
import logging
import os
-import subprocess as subprocess
import shutil
+import subprocess as subprocess
import sys
import tempfile
import time
from telemetry.core import util
from telemetry.core.backends import browser_backend
from telemetry.core.backends.chrome import chrome_browser_backend
+from telemetry.util import support_binaries
class DesktopBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
self._port = None
self._profile_dir = None
self._tmp_minidump_dir = tempfile.mkdtemp()
+ self._crash_service = None
self._SetupProfile()
shutil.rmtree(self._tmp_profile_dir)
shutil.copytree(profile_dir, self._tmp_profile_dir)
- def _LaunchBrowser(self):
- args = [self._executable]
- args.extend(self.GetBrowserStartupArgs())
- if self.browser_options.startup_url:
- args.append(self.browser_options.startup_url)
- env = os.environ.copy()
- env['CHROME_HEADLESS'] = '1' # Don't upload minidumps.
- env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
- logging.debug('Starting Chrome %s', args)
- if not self.browser_options.show_stdout:
- self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
- self._proc = subprocess.Popen(
- args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
- else:
- self._proc = subprocess.Popen(args, env=env)
-
- try:
- self._WaitForBrowserToComeUp()
- self._PostBrowserStartupInitialization()
- except:
- self.Close()
- raise
+ def _GetCrashServicePipeName(self):
+ # Ensure a unique pipe name by using the name of the temp dir.
+ return r'\\.\pipe\%s_service' % os.path.basename(self._tmp_minidump_dir)
+
+ def _StartCrashService(self):
+ os_name = self._browser.platform.GetOSName()
+ if os_name != 'win':
+ return None
+ return subprocess.Popen([
+ support_binaries.FindPath('crash_service', os_name),
+ '--no-window',
+ '--dumps-dir=%s' % self._tmp_minidump_dir,
+ '--pipe-name=%s' % self._GetCrashServicePipeName()])
+
+ def _GetCdbPath(self):
+ search_paths = [os.getenv('PROGRAMFILES(X86)', ''),
+ os.getenv('PROGRAMFILES', ''),
+ os.getenv('LOCALAPPDATA', ''),
+ os.getenv('PATH', '')]
+ possible_paths = [
+ 'Debugging Tools For Windows',
+ 'Debugging Tools For Windows (x86)',
+ 'Debugging Tools For Windows (x64)',
+ os.path.join('Windows Kits', '8.0', 'Debuggers', 'x86'),
+ os.path.join('Windows Kits', '8.0', 'Debuggers', 'x64'),
+ os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
+ 'x86'),
+ os.path.join('win_toolchain', 'vs2013_files', 'win8sdk', 'Debuggers',
+ 'x64'),
+ ]
+ for possible_path in possible_paths:
+ path = distutils.spawn.find_executable(
+ os.path.join(possible_path, 'cdb'),
+ path=os.pathsep.join(search_paths))
+ if path:
+ return path
+ return None
def HasBrowserFinishedLaunching(self):
# In addition to the functional check performed by the base class, quickly
# check if the browser process is still alive.
- self._proc.poll()
- if self._proc.returncode:
+ if not self.IsBrowserRunning():
raise exceptions.ProcessGoneException(
"Return code: %d" % self._proc.returncode)
return super(DesktopBrowserBackend, self).HasBrowserFinishedLaunching()
self._port = util.GetUnreservedAvailableLocalPort()
args.append('--remote-debugging-port=%i' % self._port)
args.append('--enable-crash-reporter-for-testing')
+ args.append('--use-mock-keychain')
if not self.is_content_shell:
args.append('--window-size=1280,1024')
if self._flash_path:
self._profile_dir = profile_dir
def Start(self):
- self._LaunchBrowser()
+ assert not self._proc, 'Must call Close() before Start()'
+
+ args = [self._executable]
+ args.extend(self.GetBrowserStartupArgs())
+ if self.browser_options.startup_url:
+ args.append(self.browser_options.startup_url)
+ env = os.environ.copy()
+ env['CHROME_HEADLESS'] = '1' # Don't upload minidumps.
+ env['BREAKPAD_DUMP_LOCATION'] = self._tmp_minidump_dir
+ env['CHROME_BREAKPAD_PIPE_NAME'] = self._GetCrashServicePipeName()
+ self._crash_service = self._StartCrashService()
+ logging.debug('Starting Chrome %s', args)
+ if not self.browser_options.show_stdout:
+ self._tmp_output_file = tempfile.NamedTemporaryFile('w', 0)
+ self._proc = subprocess.Popen(
+ args, stdout=self._tmp_output_file, stderr=subprocess.STDOUT, env=env)
+ else:
+ self._proc = subprocess.Popen(args, env=env)
+
+ try:
+ self._WaitForBrowserToComeUp()
+ self._PostBrowserStartupInitialization()
+ except:
+ self.Close()
+ raise
@property
def pid(self):
return self._tmp_profile_dir
def IsBrowserRunning(self):
- return self._proc.poll() == None
+ return self._proc and self._proc.poll() == None
def GetStandardOutput(self):
if not self._tmp_output_file:
# This can happen in the case that loading the Chrome binary fails.
# We print rather than using logging here, because that makes a
# recursive call to this function.
- print >> sys.stderr, "Can't get standard output with --show_stdout"
+ print >> sys.stderr, "Can't get standard output with --show-stdout"
return ''
self._tmp_output_file.flush()
try:
except IOError:
return ''
- def GetStackTrace(self):
- stackwalk = util.FindSupportBinary('minidump_stackwalk')
- if not stackwalk:
- logging.warning('minidump_stackwalk binary not found. Must build it to '
- 'symbolize crash dumps. Returning browser stdout.')
- return self.GetStandardOutput()
-
+ def _GetMostRecentMinidump(self):
dumps = glob.glob(os.path.join(self._tmp_minidump_dir, '*.dmp'))
if not dumps:
- logging.warning('No crash dump found. Returning browser stdout.')
- return self.GetStandardOutput()
+ return None
most_recent_dump = heapq.nlargest(1, dumps, os.path.getmtime)[0]
if os.path.getmtime(most_recent_dump) < (time.time() - (5 * 60)):
logging.warning('Crash dump is older than 5 minutes. May not be correct.')
+ return most_recent_dump
+
+ def _GetStackFromMinidump(self, minidump):
+ os_name = self._browser.platform.GetOSName()
+ if os_name == 'win':
+ cdb = self._GetCdbPath()
+ if not cdb:
+ logging.warning('cdb.exe not found.')
+ return None
+ output = subprocess.check_output([cdb, '-y', self._browser_directory,
+ '-c', '.ecxr;k30;q', '-z', minidump])
+ stack_start = output.find('ChildEBP')
+ stack_end = output.find('quit:')
+ return output[stack_start:stack_end]
+
+ stackwalk = support_binaries.FindPath('minidump_stackwalk', os_name)
+ if not stackwalk:
+ logging.warning('minidump_stackwalk binary not found.')
+ return None
symbols = glob.glob(os.path.join(self._browser_directory, '*.breakpad*'))
if not symbols:
- logging.warning('No breakpad symbols found. Returning browser stdout.')
- return self.GetStandardOutput()
+ logging.warning('No breakpad symbols found.')
+ return None
- minidump = most_recent_dump + '.stripped'
- with open(most_recent_dump, 'rb') as infile:
+ with open(minidump, 'rb') as infile:
+ minidump += '.stripped'
with open(minidump, 'wb') as outfile:
outfile.write(''.join(infile.read().partition('MDMP')[1:]))
os.makedirs(symbol_path)
shutil.copyfile(symbol, os.path.join(symbol_path, binary + '.sym'))
- error = tempfile.NamedTemporaryFile('w', 0)
- return subprocess.Popen(
- [stackwalk, minidump, symbols_path],
- stdout=subprocess.PIPE, stderr=error).communicate()[0]
+ return subprocess.check_output([stackwalk, minidump, symbols_path],
+ stderr=open(os.devnull, 'w'))
+
+ def GetStackTrace(self):
+ most_recent_dump = self._GetMostRecentMinidump()
+ if not most_recent_dump:
+ logging.warning('No crash dump found. Returning browser stdout.')
+ return self.GetStandardOutput()
+
+ stack = self._GetStackFromMinidump(most_recent_dump)
+ if not stack:
+ logging.warning('Failed to symbolize minidump. Returning browser stdout.')
+ return self.GetStandardOutput()
+
+ return stack
def __del__(self):
self.Close()
def Close(self):
super(DesktopBrowserBackend, self).Close()
- if self._proc:
+ # First, try to politely shutdown.
+ if self.IsBrowserRunning():
+ self._proc.terminate()
+ try:
+ util.WaitFor(lambda: not self.IsBrowserRunning(), timeout=5)
+ self._proc = None
+ except util.TimeoutException:
+ logging.warning('Failed to gracefully shutdown. Proceeding to kill.')
- def IsClosed():
- if not self._proc:
- return True
- return self._proc.poll() != None
-
- # Try to politely shutdown, first.
- if not IsClosed():
- self._proc.terminate()
- try:
- util.WaitFor(IsClosed, timeout=5)
- self._proc = None
- except util.TimeoutException:
- logging.warning('Failed to gracefully shutdown. Proceeding to kill.')
-
- # Kill it.
- if not IsClosed():
- self._proc.kill()
- try:
- util.WaitFor(IsClosed, timeout=10)
- except util.TimeoutException:
- raise Exception('Could not shutdown the browser.')
- finally:
- self._proc = None
+ # If it didn't comply, get more aggressive.
+ if self.IsBrowserRunning():
+ self._proc.kill()
+
+ try:
+ util.WaitFor(lambda: not self.IsBrowserRunning(), timeout=10)
+ except util.TimeoutException:
+ raise Exception('Could not shutdown the browser.')
+ finally:
+ self._proc = None
+
+ if self._crash_service:
+ self._crash_service.kill()
+ self._crash_service = None
if self._output_profile_path:
# If we need the output then double check that it exists.