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