| # 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 datetime |
| import os |
| import re |
| import shutil |
| import subprocess |
| import tempfile |
| import time |
| |
| from webkitpy.common.system.executive import ScriptError |
| |
| |
| class GDBCrashLogGenerator(object): |
| _find_pid_regex = re.compile(r'PID: (\d+) \(.*\)') |
| |
| def __init__(self, executive, name, pid, newer_than, filesystem, path_to_driver): |
| self.name = name |
| self.pid = pid |
| self.newer_than = newer_than |
| self._filesystem = filesystem |
| self._path_to_driver = path_to_driver |
| self._executive = executive |
| |
| 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 = ('ERROR: The gdb process exited with non-zero return code %s\n\n' % 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): |
| # 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 |
| |
| res = self._get_gdb_output(self._get_tmp_file_name(coredumpctl, temp_file.name)) |
| |
| return res |
| |
| return '', [] |
| |
| 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 + str(stderr or '<empty>').decode('utf8', '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(errors_str)[0] |
| |
| 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()) |