Add get_coredumpct_traces script.
This commit is contained in:
parent
2f36789ff4
commit
0187dd6ac6
161
scripts/dev/get_coredumpctl_traces.py
Normal file
161
scripts/dev/get_coredumpctl_traces.py
Normal file
@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Get qutebrowser crash information and stacktraces from coredumpctl."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import collections
|
||||
import os.path
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
os.pardir))
|
||||
|
||||
from scripts import utils
|
||||
|
||||
|
||||
Line = collections.namedtuple('Line', 'time, pid, uid, gid, sig, present, exe')
|
||||
|
||||
|
||||
def _convert_present(data):
|
||||
"""Convert " "/"*" to True/False for parse_coredumpctl_line."""
|
||||
if data == '*':
|
||||
return True
|
||||
elif data == ' ':
|
||||
return False
|
||||
else:
|
||||
raise ValueError(data)
|
||||
|
||||
|
||||
def parse_coredumpctl_line(line):
|
||||
"""Parse a given string coming from coredumpctl and return a Line object.
|
||||
|
||||
Example input:
|
||||
Mon 2015-09-28 23:22:24 CEST 10606 1000 1000 11 /usr/bin/python3.4
|
||||
"""
|
||||
fields = {
|
||||
'time': (0, 28, str),
|
||||
'pid': (29, 35, int),
|
||||
'uid': (36, 41, int),
|
||||
'gid': (42, 47, int),
|
||||
'sig': (48, 51, int),
|
||||
'present': (52, 53, _convert_present),
|
||||
'exe': (54, None, str),
|
||||
}
|
||||
|
||||
data = {}
|
||||
for name, (start, end, converter) in fields.items():
|
||||
data[name] = converter(line[start:end])
|
||||
return Line(**data)
|
||||
|
||||
|
||||
def get_info(pid):
|
||||
"""Get and parse "coredumpctl info" output for the given PID."""
|
||||
data = {}
|
||||
output = subprocess.check_output(['coredumpctl', 'info', str(pid)])
|
||||
output = output.decode('utf-8')
|
||||
for line in output.split('\n'):
|
||||
if not line.strip():
|
||||
continue
|
||||
key, value = line.split(':', maxsplit=1)
|
||||
data[key.strip()] = value.strip()
|
||||
return data
|
||||
|
||||
|
||||
def is_qutebrowser_dump(parsed):
|
||||
"""Check if the given Line is a qutebrowser dump."""
|
||||
basename = os.path.basename(parsed.exe)
|
||||
if basename in ['python', 'python3', 'python3.4', 'python3.5']:
|
||||
info = get_info(parsed.pid)
|
||||
try:
|
||||
cmdline = info['Command Line']
|
||||
except KeyError:
|
||||
return True
|
||||
else:
|
||||
return '-m qutebrowser' in cmdline
|
||||
elif basename == 'qutebrowser':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def dump_infos_gdb(parsed):
|
||||
"""Dump all needed infos for the given crash using gdb."""
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
coredump = os.path.join(tempdir, 'dump')
|
||||
subprocess.check_call(['coredumpctl', 'dump', '-o', coredump,
|
||||
str(parsed.pid)])
|
||||
subprocess.check_call(['gdb', parsed.exe, coredump,
|
||||
'-ex', 'info threads',
|
||||
'-ex', 'thread apply all bt full',
|
||||
'-ex', 'quit'])
|
||||
|
||||
|
||||
def dump_infos(parsed):
|
||||
"""Dump all possible infos for the given crash."""
|
||||
if not parsed.present:
|
||||
info = get_info(parsed.pid)
|
||||
print("{}: Signal {} with no coredump: {}".format(
|
||||
parsed.time, info.get('Signal', None),
|
||||
info.get('Command Line', None)))
|
||||
else:
|
||||
print('\n\n\n')
|
||||
utils.print_title('{} - {}'.format(parsed.time, parsed.pid))
|
||||
sys.stdout.flush()
|
||||
dump_infos_gdb(parsed)
|
||||
|
||||
|
||||
def check_prerequisites():
|
||||
"""Check if coredumpctl/gdb are installed."""
|
||||
for binary in ['coredumpctl', 'gdb']:
|
||||
try:
|
||||
subprocess.check_call([binary, '--version'])
|
||||
except FileNotFoundError:
|
||||
print("{} is needed to run this script!".format(binary),
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
check_prerequisites()
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--all', help="Also list crashes without coredumps.",
|
||||
action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
coredumps = subprocess.check_output(['coredumpctl', 'list'])
|
||||
lines = coredumps.decode('utf-8').split('\n')
|
||||
for line in lines[1:]:
|
||||
if not line.strip():
|
||||
continue
|
||||
parsed = parse_coredumpctl_line(line)
|
||||
if not parsed.present and not args.all:
|
||||
continue
|
||||
if is_qutebrowser_dump(parsed):
|
||||
dump_infos(parsed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user