3 Simple crash handling application for autotest
5 @copyright Red Hat Inc 2009
6 @author Lucas Meneghel Rodrigues <lmr@redhat.com>
8 import sys
, os
, commands
, glob
, shutil
, syslog
, re
, time
, random
, string
11 def generate_random_string(length
):
13 Return a random string using alphanumeric characters.
15 @length: length of the string that will be generated.
17 r
= random
.SystemRandom()
19 chars
= string
.letters
+ string
.digits
21 str += r
.choice(chars
)
26 def get_parent_pid(pid
):
28 Returns the parent PID for a given PID, converted to an integer.
30 @param pid: Process ID.
33 ppid
= int(open('/proc/%s/stat' % pid
).read().split()[3])
35 # It is not possible to determine the parent because the process
36 # already left the process table.
42 def write_to_file(filename
, data
, report
=False):
44 Write contents to a given file path specified. If not specified, the file
47 @param file_path: Path to a given file.
48 @param data: File contents.
49 @param report: Whether we'll use GDB to get a backtrace report of the
52 f
= open(filename
, 'w')
64 def get_results_dir_list(pid
, core_dir_basename
):
66 Get all valid output directories for the core file and the report. It works
67 by inspecting files created by each test on /tmp and verifying if the
68 PID of the process that crashed is a child or grandchild of the autotest
69 test process. If it can't find any relationship (maybe a daemon that died
70 during a test execution), it will write the core file to the debug dirs
71 of all tests currently being executed. If there are no active autotest
72 tests at a particular moment, it will return a list with ['/tmp'].
74 @param pid: PID for the process that generated the core
75 @param core_dir_basename: Basename for the directory that will hold both
76 the core dump and the crash report.
79 for debugdir_file
in glob
.glob("/tmp/autotest_results_dir.*"):
80 a_pid
= os
.path
.splitext(debugdir_file
)[1]
81 results_dir
= open(debugdir_file
).read().strip()
82 pid_dir_dict
[a_pid
] = os
.path
.join(results_dir
, core_dir_basename
)
85 # If a bug occurs and we can't grab the PID for the process that died, just
86 # return all directories available and write to all of them.
89 if pid
in pid_dir_dict
:
90 results_dir_list
.append(pid_dir_dict
[pid
])
91 pid
= get_parent_pid(pid
)
93 results_dir_list
= pid_dir_dict
.values()
95 return (results_dir_list
or
96 pid_dir_dict
.values() or
97 [os
.path
.join("/tmp", core_dir_basename
)])
100 def get_info_from_core(path
):
102 Reads a core file and extracts a dictionary with useful core information.
104 Right now, the only information extracted is the full executable name.
106 @param path: Path to core file.
109 output
= commands
.getoutput('gdb -c %s batch' % path
)
110 path_pattern
= re
.compile("Core was generated by `([^\0]+)'", re
.IGNORECASE
)
111 match
= re
.findall(path_pattern
, output
)
113 # Sometimes the command line args come with the core, so get rid of them
115 if os
.path
.isfile(m
):
119 if full_exe_path
is None:
120 syslog
.syslog("Could not determine from which application core file %s "
123 return {'full_exe_path': full_exe_path
}
126 def gdb_report(path
):
128 Use GDB to produce a report with information about a given core.
130 @param path: Path to core file.
132 # Get full command path
133 exe_path
= get_info_from_core(path
)['full_exe_path']
134 basedir
= os
.path
.dirname(path
)
135 gdb_command_path
= os
.path
.join(basedir
, 'gdb_cmd')
137 if exe_path
is not None:
138 # Write a command file for GDB
139 gdb_command
= 'bt full\n'
140 write_to_file(gdb_command_path
, gdb_command
)
142 # Take a backtrace from the running program
143 gdb_cmd
= ('gdb -e %s -c %s -x %s -n -batch -quiet' %
144 (exe_path
, path
, gdb_command_path
))
145 backtrace
= commands
.getoutput(gdb_cmd
)
146 # Sanitize output before passing it to the report
147 backtrace
= backtrace
.decode('utf-8', 'ignore')
150 backtrace
= ("Could not determine backtrace for core file %s" % path
)
152 # Composing the format_dict
153 report
= "Program: %s\n" % exe_path
154 if crashed_pid
is not None:
155 report
+= "PID: %s\n" % crashed_pid
156 if signal
is not None:
157 report
+= "Signal: %s\n" % signal
158 if hostname
is not None:
159 report
+= "Hostname: %s\n" % hostname
160 if crash_time
is not None:
161 report
+= ("Time of the crash (according to kernel): %s\n" %
162 time
.ctime(float(crash_time
)))
163 report
+= "Program backtrace:\n%s\n" % backtrace
165 report_path
= os
.path
.join(basedir
, 'report')
166 write_to_file(report_path
, report
)
169 def write_cores(core_data
, dir_list
):
171 Write core files to all directories, optionally providing reports.
173 @param core_data: Contents of the core file.
174 @param dir_list: List of directories the cores have to be written.
175 @param report: Whether reports are to be generated for those core files.
177 syslog
.syslog("Writing core files to %s" % dir_list
)
178 for result_dir
in dir_list
:
179 if not os
.path
.isdir(result_dir
):
180 os
.makedirs(result_dir
)
181 core_path
= os
.path
.join(result_dir
, 'core')
182 core_path
= write_to_file(core_path
, core_file
, report
=True)
185 if __name__
== "__main__":
186 syslog
.openlog('AutotestCrashHandler', 0, syslog
.LOG_DAEMON
)
187 global crashed_pid
, crash_time
, uid
, signal
, hostname
, exe
189 full_functionality
= False
191 crashed_pid
, crash_time
, uid
, signal
, hostname
, exe
= sys
.argv
[1:]
192 full_functionality
= True
193 except ValueError, e
:
194 # Probably due a kernel bug, we can't exactly map the parameters
195 # passed to this script. So we have to reduce the functionality
196 # of the script (just write the core at a fixed place).
197 syslog
.syslog("Unable to unpack parameters passed to the "
198 "script. Operating with limited functionality.")
199 crashed_pid
, crash_time
, uid
, signal
, hostname
, exe
= (None, None,
203 if full_functionality
:
204 core_dir_name
= 'crash.%s.%s' % (exe
, crashed_pid
)
206 core_dir_name
= 'core.%s' % generate_random_string(4)
208 # Get the filtered results dir list
209 results_dir_list
= get_results_dir_list(crashed_pid
, core_dir_name
)
211 # Write the core file to the appropriate directory
212 # (we are piping it to this script)
213 core_file
= sys
.stdin
.read()
215 if (exe
is not None) and (crashed_pid
is not None):
216 syslog
.syslog("Application %s, PID %s crashed" % (exe
, crashed_pid
))
217 write_cores(core_file
, results_dir_list
)
220 syslog
.syslog("Crash handler had a problem: %s" % e
)