# 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).encode('utf8', 'ignore')) + 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 '', errors='ignore').splitlines() return crash_log, errors def generate_crash_log(self, stdout, stderr): pid_representation = str(self.pid or '') 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 '', 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())