xref: /aosp_15_r20/external/autotest/client/tools/crash_handler.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/python3
2*9c5db199SXin Li"""
3*9c5db199SXin LiSimple crash handling application for autotest
4*9c5db199SXin Li
5*9c5db199SXin Li@copyright Red Hat Inc 2009
6*9c5db199SXin Li@author Lucas Meneghel Rodrigues <[email protected]>
7*9c5db199SXin Li"""
8*9c5db199SXin Lifrom __future__ import absolute_import
9*9c5db199SXin Lifrom __future__ import division
10*9c5db199SXin Lifrom __future__ import print_function
11*9c5db199SXin Li
12*9c5db199SXin Liimport glob
13*9c5db199SXin Liimport os
14*9c5db199SXin Liimport random
15*9c5db199SXin Liimport re
16*9c5db199SXin Liimport shutil
17*9c5db199SXin Liimport six
18*9c5db199SXin Liimport string
19*9c5db199SXin Liimport subprocess
20*9c5db199SXin Liimport sys
21*9c5db199SXin Liimport syslog
22*9c5db199SXin Liimport time
23*9c5db199SXin Li
24*9c5db199SXin Li
25*9c5db199SXin Lidef generate_random_string(length):
26*9c5db199SXin Li    """
27*9c5db199SXin Li    Return a random string using alphanumeric characters.
28*9c5db199SXin Li
29*9c5db199SXin Li    @length: length of the string that will be generated.
30*9c5db199SXin Li    """
31*9c5db199SXin Li    r = random.SystemRandom()
32*9c5db199SXin Li    str = ""
33*9c5db199SXin Li    chars = string.letters + string.digits
34*9c5db199SXin Li    while length > 0:
35*9c5db199SXin Li        str += r.choice(chars)
36*9c5db199SXin Li        length -= 1
37*9c5db199SXin Li    return str
38*9c5db199SXin Li
39*9c5db199SXin Li
40*9c5db199SXin Lidef get_parent_pid(pid):
41*9c5db199SXin Li    """
42*9c5db199SXin Li    Returns the parent PID for a given PID, converted to an integer.
43*9c5db199SXin Li
44*9c5db199SXin Li    @param pid: Process ID.
45*9c5db199SXin Li    """
46*9c5db199SXin Li    try:
47*9c5db199SXin Li        ppid = int(open('/proc/%s/stat' % pid).read().split()[3])
48*9c5db199SXin Li    except:
49*9c5db199SXin Li        # It is not possible to determine the parent because the process
50*9c5db199SXin Li        # already left the process table.
51*9c5db199SXin Li        ppid = 1
52*9c5db199SXin Li
53*9c5db199SXin Li    return ppid
54*9c5db199SXin Li
55*9c5db199SXin Li
56*9c5db199SXin Lidef write_to_file(filename, data, report=False):
57*9c5db199SXin Li    """
58*9c5db199SXin Li    Write contents to a given file path specified. If not specified, the file
59*9c5db199SXin Li    will be created.
60*9c5db199SXin Li
61*9c5db199SXin Li    @param file_path: Path to a given file.
62*9c5db199SXin Li    @param data: File contents.
63*9c5db199SXin Li    @param report: Whether we'll use GDB to get a backtrace report of the
64*9c5db199SXin Li                   file.
65*9c5db199SXin Li    """
66*9c5db199SXin Li    f = open(filename, 'w')
67*9c5db199SXin Li    try:
68*9c5db199SXin Li        f.write(data)
69*9c5db199SXin Li    finally:
70*9c5db199SXin Li        f.close()
71*9c5db199SXin Li
72*9c5db199SXin Li    if report:
73*9c5db199SXin Li        gdb_report(filename)
74*9c5db199SXin Li
75*9c5db199SXin Li    return filename
76*9c5db199SXin Li
77*9c5db199SXin Li
78*9c5db199SXin Lidef get_results_dir_list(pid, core_dir_basename):
79*9c5db199SXin Li    """
80*9c5db199SXin Li    Get all valid output directories for the core file and the report. It works
81*9c5db199SXin Li    by inspecting files created by each test on /tmp and verifying if the
82*9c5db199SXin Li    PID of the process that crashed is a child or grandchild of the autotest
83*9c5db199SXin Li    test process. If it can't find any relationship (maybe a daemon that died
84*9c5db199SXin Li    during a test execution), it will write the core file to the debug dirs
85*9c5db199SXin Li    of all tests currently being executed. If there are no active autotest
86*9c5db199SXin Li    tests at a particular moment, it will return a list with ['/tmp'].
87*9c5db199SXin Li
88*9c5db199SXin Li    @param pid: PID for the process that generated the core
89*9c5db199SXin Li    @param core_dir_basename: Basename for the directory that will hold both
90*9c5db199SXin Li            the core dump and the crash report.
91*9c5db199SXin Li    """
92*9c5db199SXin Li    pid_dir_dict = {}
93*9c5db199SXin Li    for debugdir_file in glob.glob("/tmp/autotest_results_dir.*"):
94*9c5db199SXin Li        a_pid = os.path.splitext(debugdir_file)[1]
95*9c5db199SXin Li        results_dir = open(debugdir_file).read().strip()
96*9c5db199SXin Li        pid_dir_dict[a_pid] = os.path.join(results_dir, core_dir_basename)
97*9c5db199SXin Li
98*9c5db199SXin Li    results_dir_list = []
99*9c5db199SXin Li    # If a bug occurs and we can't grab the PID for the process that died, just
100*9c5db199SXin Li    # return all directories available and write to all of them.
101*9c5db199SXin Li    if pid is not None:
102*9c5db199SXin Li        while pid > 1:
103*9c5db199SXin Li            if pid in pid_dir_dict:
104*9c5db199SXin Li                results_dir_list.append(pid_dir_dict[pid])
105*9c5db199SXin Li            pid = get_parent_pid(pid)
106*9c5db199SXin Li    else:
107*9c5db199SXin Li        results_dir_list = list(pid_dir_dict.values())
108*9c5db199SXin Li
109*9c5db199SXin Li    return (results_dir_list or
110*9c5db199SXin Li            list(pid_dir_dict.values()) or
111*9c5db199SXin Li            [os.path.join("/tmp", core_dir_basename)])
112*9c5db199SXin Li
113*9c5db199SXin Li
114*9c5db199SXin Lidef get_info_from_core(path):
115*9c5db199SXin Li    """
116*9c5db199SXin Li    Reads a core file and extracts a dictionary with useful core information.
117*9c5db199SXin Li
118*9c5db199SXin Li    Right now, the only information extracted is the full executable name.
119*9c5db199SXin Li
120*9c5db199SXin Li    @param path: Path to core file.
121*9c5db199SXin Li    """
122*9c5db199SXin Li    full_exe_path = None
123*9c5db199SXin Li    output = subprocess.getoutput('gdb -c %s batch' % path)
124*9c5db199SXin Li    path_pattern = re.compile("Core was generated by `([^\0]+)'", re.IGNORECASE)
125*9c5db199SXin Li    match = re.findall(path_pattern, output)
126*9c5db199SXin Li    for m in match:
127*9c5db199SXin Li        # Sometimes the command line args come with the core, so get rid of them
128*9c5db199SXin Li        m = m.split(" ")[0]
129*9c5db199SXin Li        if os.path.isfile(m):
130*9c5db199SXin Li            full_exe_path = m
131*9c5db199SXin Li            break
132*9c5db199SXin Li
133*9c5db199SXin Li    if full_exe_path is None:
134*9c5db199SXin Li        syslog.syslog("Could not determine from which application core file %s "
135*9c5db199SXin Li                      "is from" % path)
136*9c5db199SXin Li
137*9c5db199SXin Li    return {'full_exe_path': full_exe_path}
138*9c5db199SXin Li
139*9c5db199SXin Li
140*9c5db199SXin Lidef gdb_report(path):
141*9c5db199SXin Li    """
142*9c5db199SXin Li    Use GDB to produce a report with information about a given core.
143*9c5db199SXin Li
144*9c5db199SXin Li    @param path: Path to core file.
145*9c5db199SXin Li    """
146*9c5db199SXin Li    # Get full command path
147*9c5db199SXin Li    exe_path = get_info_from_core(path)['full_exe_path']
148*9c5db199SXin Li    basedir = os.path.dirname(path)
149*9c5db199SXin Li    gdb_command_path = os.path.join(basedir, 'gdb_cmd')
150*9c5db199SXin Li
151*9c5db199SXin Li    if exe_path is not None:
152*9c5db199SXin Li        # Write a command file for GDB
153*9c5db199SXin Li        gdb_command = 'bt full\n'
154*9c5db199SXin Li        write_to_file(gdb_command_path, gdb_command)
155*9c5db199SXin Li
156*9c5db199SXin Li        # Take a backtrace from the running program
157*9c5db199SXin Li        gdb_cmd = ('gdb -e %s -c %s -x %s -n -batch -quiet' %
158*9c5db199SXin Li                   (exe_path, path, gdb_command_path))
159*9c5db199SXin Li        backtrace = subprocess.getoutput(gdb_cmd)
160*9c5db199SXin Li        # Sanitize output before passing it to the report
161*9c5db199SXin Li        backtrace = six.ensure_text(backtrace, 'utf-8', 'ignore')
162*9c5db199SXin Li    else:
163*9c5db199SXin Li        exe_path = "Unknown"
164*9c5db199SXin Li        backtrace = ("Could not determine backtrace for core file %s" % path)
165*9c5db199SXin Li
166*9c5db199SXin Li    # Composing the format_dict
167*9c5db199SXin Li    report = "Program: %s\n" % exe_path
168*9c5db199SXin Li    if crashed_pid is not None:
169*9c5db199SXin Li        report += "PID: %s\n" % crashed_pid
170*9c5db199SXin Li    if signal is not None:
171*9c5db199SXin Li        report += "Signal: %s\n" % signal
172*9c5db199SXin Li    if hostname is not None:
173*9c5db199SXin Li        report += "Hostname: %s\n" % hostname
174*9c5db199SXin Li    if crash_time is not None:
175*9c5db199SXin Li        report += ("Time of the crash (according to kernel): %s\n" %
176*9c5db199SXin Li                   time.ctime(float(crash_time)))
177*9c5db199SXin Li    report += "Program backtrace:\n%s\n" % backtrace
178*9c5db199SXin Li
179*9c5db199SXin Li    report_path = os.path.join(basedir, 'report')
180*9c5db199SXin Li    write_to_file(report_path, report)
181*9c5db199SXin Li
182*9c5db199SXin Li
183*9c5db199SXin Lidef write_cores(core_data, dir_list):
184*9c5db199SXin Li    """
185*9c5db199SXin Li    Write core files to all directories, optionally providing reports.
186*9c5db199SXin Li
187*9c5db199SXin Li    @param core_data: Contents of the core file.
188*9c5db199SXin Li    @param dir_list: List of directories the cores have to be written.
189*9c5db199SXin Li    @param report: Whether reports are to be generated for those core files.
190*9c5db199SXin Li    """
191*9c5db199SXin Li    syslog.syslog("Writing core files to %s" % dir_list)
192*9c5db199SXin Li    for result_dir in dir_list:
193*9c5db199SXin Li        if not os.path.isdir(result_dir):
194*9c5db199SXin Li            os.makedirs(result_dir)
195*9c5db199SXin Li        core_path = os.path.join(result_dir, 'core')
196*9c5db199SXin Li        core_path = write_to_file(core_path, core_file, report=True)
197*9c5db199SXin Li
198*9c5db199SXin Li
199*9c5db199SXin Liif __name__ == "__main__":
200*9c5db199SXin Li    syslog.openlog('AutotestCrashHandler', 0, syslog.LOG_DAEMON)
201*9c5db199SXin Li    global crashed_pid, crash_time, uid, signal, hostname, exe
202*9c5db199SXin Li    try:
203*9c5db199SXin Li        full_functionality = False
204*9c5db199SXin Li        try:
205*9c5db199SXin Li            crashed_pid, crash_time, uid, signal, hostname, exe = sys.argv[1:]
206*9c5db199SXin Li            full_functionality = True
207*9c5db199SXin Li        except ValueError as e:
208*9c5db199SXin Li            # Probably due a kernel bug, we can't exactly map the parameters
209*9c5db199SXin Li            # passed to this script. So we have to reduce the functionality
210*9c5db199SXin Li            # of the script (just write the core at a fixed place).
211*9c5db199SXin Li            syslog.syslog("Unable to unpack parameters passed to the "
212*9c5db199SXin Li                          "script. Operating with limited functionality.")
213*9c5db199SXin Li            crashed_pid, crash_time, uid, signal, hostname, exe = (None, None,
214*9c5db199SXin Li                                                                   None, None,
215*9c5db199SXin Li                                                                   None, None)
216*9c5db199SXin Li
217*9c5db199SXin Li        if full_functionality:
218*9c5db199SXin Li            core_dir_name = 'crash.%s.%s' % (exe, crashed_pid)
219*9c5db199SXin Li        else:
220*9c5db199SXin Li            core_dir_name = 'core.%s' % generate_random_string(4)
221*9c5db199SXin Li
222*9c5db199SXin Li        # Get the filtered results dir list
223*9c5db199SXin Li        results_dir_list = get_results_dir_list(crashed_pid, core_dir_name)
224*9c5db199SXin Li
225*9c5db199SXin Li        # Write the core file to the appropriate directory
226*9c5db199SXin Li        # (we are piping it to this script)
227*9c5db199SXin Li        core_file = sys.stdin.read()
228*9c5db199SXin Li
229*9c5db199SXin Li        if (exe is not None) and (crashed_pid is not None):
230*9c5db199SXin Li            syslog.syslog("Application %s, PID %s crashed" % (exe, crashed_pid))
231*9c5db199SXin Li        write_cores(core_file, results_dir_list)
232*9c5db199SXin Li
233*9c5db199SXin Li    except Exception as e:
234*9c5db199SXin Li        syslog.syslog("Crash handler had a problem: %s" % e)
235