blob: e349b6c0193673a43790347588f0d12407208c2a [file] [log] [blame]
# Copyright (C) 2013 University of Szeged
# This module is a refactoring from gtk.py, Copyright (C) 2013 Igalia S.L
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import re
import subprocess
import tempfile
import time
from webkitcorepy import string_utils
from webkitpy.common.system.executive import ScriptError
from webkitpy.common.webkit_finder import WebKitFinder
class GDBCrashLogGenerator(object):
_find_pid_regex = re.compile(r'PID: (\d+) \(.*\)')
def __init__(self, executive, name, pid, newer_than, filesystem, path_to_driver, port_name, configuration):
self.name = name
self.pid = pid
self.newer_than = newer_than
self._filesystem = filesystem
self._path_to_driver = path_to_driver
self._executive = executive
self._port_name = port_name
self._configuration = configuration
self._webkit_finder = WebKitFinder(filesystem)
def _get_gdb_output(self, coredump_path):
process_name = self._filesystem.join(os.path.dirname(str(self._path_to_driver())), self.name)
cmd = ['gdb', '-ex', 'thread apply all bt 1024', '--batch', process_name, coredump_path]
proc = self._executive.popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
errors = [stderr_line.strip().decode('utf8', 'ignore') for stderr_line in stderr.splitlines()]
if proc.returncode != 0:
stdout = (b'ERROR: The gdb process exited with non-zero return code %s\n\n' % str(proc.returncode)) + stdout
return (stdout.decode('utf8', 'ignore'), errors)
def _get_tmp_file_name(self, coredumpctl, filename):
if coredumpctl[0] == 'flatpak-spawn':
return "/run/host/" + filename
return filename
def _get_trace_from_systemd(self, coredumpctl, pid):
if os.path.isfile("/.flatpak-info"):
return self._get_trace_from_flatpak()
# Letting up to 5 seconds for the backtrace to be generated on the systemd side
for try_number in range(5):
if try_number != 0:
# Looping, it means we consider the logs might not be ready yet.
time.sleep(1)
try:
info = self._executive.run_command(coredumpctl + ['info', "--since=" + time.strftime("%a %Y-%m-%d %H:%M:%S %Z", time.localtime(self.newer_than))],
return_stderr=True)
except (ScriptError, OSError):
continue
found_newer = False
# Coredumpctl will use the latest core dump with the specified PID
# assume it is the right one.
pids = self._find_pid_regex.findall(info)
if not pids:
continue
pid = pids[0]
with tempfile.NamedTemporaryFile() as temp_file:
if self._executive.run_command(coredumpctl + ['dump', pid, '--output',
temp_file.name], return_exit_code=True):
continue
return self._get_gdb_output(self._get_tmp_file_name(coredumpctl, temp_file.name))
return '', []
def _get_trace_from_flatpak(self):
if self.newer_than:
coredump_since = "--gdb-stack-trace=@%f" % self.newer_than
else:
coredump_since = "--gdb-stack-trace"
webkit_flatpak_path = self._webkit_finder.path_to_script('webkit-flatpak')
cmd = ['flatpak-spawn', '--host']
# Forward WEBKIT_FLATPAK_USER_DIR so webkit-flatpak can use the same flatpak
# install as the current one.
user_dir = os.environ.get('WEBKIT_FLATPAK_USER_DIR')
if user_dir:
cmd.append("--env=WEBKIT_FLATPAK_USER_DIR=%s" % user_dir)
cmd.extend([webkit_flatpak_path, '--%s' % self._port_name,
"--%s" % self._configuration.lower(), "--verbose", coredump_since])
proc = self._executive.popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
crash_log, stderr = proc.communicate()
crash_log = string_utils.decode(crash_log, errors='ignore')
errors = string_utils.decode(stderr or '<empty>', errors='ignore').splitlines()
return crash_log, errors
def generate_crash_log(self, stdout, stderr):
pid_representation = str(self.pid or '<unknown>')
log_directory = os.environ.get("WEBKIT_CORE_DUMPS_DIRECTORY")
errors = []
crash_log = ''
expected_crash_dump_filename = "core-pid_%s.dump" % pid_representation
proc_name = "%s" % (self.name)
def match_filename(filesystem, directory, filename):
if self.pid:
return filename == expected_crash_dump_filename
return filename.find(self.name) > -1
# Poor man which, ignore any failure.
for coredumpctl in [['coredumpctl'], ['flatpak-spawn', '--host', 'coredumpctl'], []]:
try:
if not self._executive.run_command(coredumpctl, return_exit_code=True):
break
except:
continue
if log_directory:
dumps = self._filesystem.files_under(
log_directory, file_filter=match_filename)
if dumps:
# Get the most recent coredump matching the pid and/or process name.
coredump_path = list(reversed(sorted(dumps)))[0]
if not self.newer_than or self._filesystem.mtime(coredump_path) > self.newer_than:
crash_log, errors = self._get_gdb_output(coredump_path)
elif coredumpctl:
crash_log, errors = self._get_trace_from_systemd(coredumpctl, pid_representation)
stderr_lines = errors + string_utils.decode(stderr or '<empty>', errors='ignore').splitlines()
errors_str = '\n'.join(('STDERR: ' + stderr_line) for stderr_line in stderr_lines)
cppfilt_proc = self._executive.popen(
['c++filt'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errors_str = cppfilt_proc.communicate(string_utils.encode(errors_str))[0]
errors_str = string_utils.decode(errors_str, errors='ignore')
if not crash_log:
if not log_directory:
log_directory = "/path/to/coredumps"
core_pattern = self._filesystem.join(log_directory, "core-pid_%p.dump")
crash_log = """\
Coredump %(expected_crash_dump_filename)s not found. To enable crash logs:
- run this command as super-user: echo "%(core_pattern)s" > /proc/sys/kernel/core_pattern
- enable core dumps: ulimit -c unlimited
- set the WEBKIT_CORE_DUMPS_DIRECTORY environment variable: export WEBKIT_CORE_DUMPS_DIRECTORY=%(log_directory)s
""" % locals()
return (stderr, """\
crash log for %(proc_name)s (pid %(pid_representation)s):
%(crash_log)s
%(errors_str)s""" % locals())