2 #####################################################################
3 # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
4 # Version 2, December 2004
6 # Copyright (C) 2011-2012 Ludovic Fauvet <etix@videolan.org>
7 # Jean-Baptiste Kempf <jb@videolan.org>
9 # Everyone is permitted to copy and distribute verbatim or modified
10 # copies of this license document, and changing it is allowed as long
11 # as the name is changed.
13 # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
14 # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
16 # 0. You just DO WHAT THE FUCK YOU WANT TO.
17 #####################################################################
19 # This script can be started in two ways:
20 # - Without any arguments:
21 # The script will search for stacktrace in the WORKDIR, process
22 # them and dispatch them in their respective subdirectories.
23 # - With a stacktrace as only argument:
24 # The script will write the output on stdout and exit immediately
25 # after the stacktrace has been processed.
26 # The input file will stay in place, untouched.
28 # NOTE: Due to a bug in the mingw32-binutils > 2.19 the section
29 # .gnu_debuglink in the binary file is trimmed thus preventing
30 # gdb to find the associated symbols. This script will
31 # work around this issue and rerun gdb for each dbg file.
33 #####################################################################
36 VLC_BIN = "/home/videolan/vlc/" + VLC_VERSION + "/vlc-" VLC_VERSION + "/vlc.exe"
37 VLC_BASE_DIR = "/home/videolan/vlc/" + VLC_VERSION + "/vlc-" + VLC_VERSION + "/"
38 VLC_SYMBOLS_DIR = "/home/videolan/vlc/" + VLC_VERSION + "/symbols-" + VLC_VERSION + "/"
39 WORKDIR = "/srv/ftp/crashes-win32"
40 FILE_MATCH = r"^\d{14}$"
42 GDB_CMD = "gdb --exec=%(VLC_BIN)s --symbols=%(VLC_SYMBOLS_DIR)s%(DBG_FILE)s.dbg --batch -x %(BATCH_FILE)s"
44 EMAIL_TO = "bugreporter -- videolan.org"
45 EMAIL_FROM = "crashes@crash.videolan.org"
46 EMAIL_SUBJECT = "[CRASH] New Win32 crash report"
51 This crash has been reported automatically and might be incomplete and/or broken.
52 Windows version: %(WIN32_VERSION)s
60 import os, sys, re, tempfile
61 import string, shlex, subprocess
62 import smtplib, datetime, shutil
64 from email.mime.text import MIMEText
67 def processFile(filename):
68 print "Processing " + filename
71 f = open(filename, 'r')
72 # Read (and repair) the input file
73 content = "".join(filter(lambda x: x in string.printable, f.read()))
76 if os.path.getsize(filename) < 10:
81 # Check if VLC version match
82 if not isValidVersion(content):
83 print("Invalid VLC version")
84 moveFile(filename, outdated = True)
88 win32_version = getWinVersion(content) or 'unknown'
90 # Map eip <--> library
91 mapping = mapLibraries(content)
93 print("Stacktrace not found")
97 # Associate all eip to their respective lib
105 sortedEIP,delta_libs = sortEIP(content,mapping)
106 # Compute the stacktrace using GDB
107 eipmap = findSymbols(sortedEIP)
108 # Generate the body of the email
109 body = genEmailBody(mapping, eipmap, delta_libs)
115 # Finally archive the stacktrace
116 moveFile(filename, outdated = False)
118 def isValidVersion(content):
119 pattern = re.compile(r"^VLC=%s " % VLC_VERSION, re.MULTILINE)
120 res = pattern.search(content)
121 return True if res else False
123 def getWinVersion(content):
124 pattern = re.compile(r"^OS=(.*)$", re.MULTILINE)
125 res = pattern.search(content)
130 def getDiffAddress(content, name):
131 plugin_name_section = content.find(name)
132 if plugin_name_section < 0:
135 begin_index = content.rfind("\n", 0, plugin_name_section) + 1
136 end_index = content.find("|", begin_index)
138 tmp_index = name.rfind('plugins\\')
139 libname = name[tmp_index :].replace("\\", "/")
140 full_path = VLC_BASE_DIR + libname
142 if not os.path.isfile(full_path):
145 cmd = "objdump -p " + full_path + " |grep ImageBase -|cut -f2-"
146 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().strip()
148 diff = int(content[begin_index:end_index], 16) - int(p, 16)
151 def mapLibraries(content):
152 stacktrace_section = content.find("[stacktrace]")
153 if stacktrace_section < 0:
156 stacklines = content[stacktrace_section:]
157 stacklines = stacklines.splitlines()
158 pattern = re.compile(r"^([0-9a-fA-F]+)\|(.+)$")
161 for line in stacklines:
162 m = pattern.match(line)
165 mapping.append(m.group(1, 2))
167 if len(mapping) == 0:
172 def sortEIP(content, mapping):
173 # Merge all EIP mapping to the same library
177 # Extract the library name (without the full path)
178 index = item[1].rfind('\\')
179 libname = item[1][index + 1:]
181 # Append the eip to its respective lib
182 if libname not in libs:
184 diff = getDiffAddress(content, item[1])
186 libs_address[libname] = diff
188 libs_address[libname] = 0
190 libs[libname].append(int(item[0],16) - libs_address[libname])
192 return libs,libs_address
195 def findSymbols(sortedEIP):
198 for k, v in sortedEIP.items():
199 # Create the gdb batchfile
200 batchfile = tempfile.NamedTemporaryFile(mode="w")
201 batchfile.write("set print symbol-filename on\n")
203 # Append all eip for this lib
205 batchfile.write('p/a %s\n' % hex(eip))
208 # Generate the command line
209 cmd = GDB_CMD % {"VLC_BIN": VLC_BIN, "VLC_SYMBOLS_DIR": VLC_SYMBOLS_DIR, "DBG_FILE": k, "BATCH_FILE": batchfile.name}
210 args = shlex.split(cmd)
212 # Start GDB and get result
213 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
216 gdb_pattern = re.compile(r"^\$\d+ = (.+)$")
218 while p.poll() == None:
219 o = p.stdout.readline()
222 m = gdb_pattern.match(o)
224 #print("LINE: [%s]" % m.group(1))
225 eipmap[v[cnt]] = m.group(1)
231 def genEmailBody(mapping, eipmap, delta_libs):
235 index = item[1].rfind('\\')
236 libname = item[1][index + 1:]
237 print(int(item[0],16), delta_libs[libname])
240 stacktrace += "%d. %s [in %s]\n" % (cnt, eipmap[int(item[0],16)-delta_libs[libname]], item[1])
242 stacktrace = stacktrace.rstrip('\n')
243 return EMAIL_BODY % {"STACKTRACE": stacktrace, "WIN32_VERSION": win32_version}
248 msg['Subject'] = EMAIL_SUBJECT
249 msg['From'] = EMAIL_FROM
254 s.connect("127.0.0.1")
255 s.sendmail(EMAIL_FROM, [EMAIL_TO], msg.as_string())
258 def moveFile(filename, outdated = False):
259 today = datetime.datetime.now().strftime("%Y%m%d")
260 today_path = "%s/%s" % (WORKDIR, today)
261 if not os.path.isdir(today_path):
264 shutil.move(filename, "%s/%s" % (today_path, os.path.basename(filename)))
266 outdated_path = "%s/outdated/" % today_path
267 if not os.path.isdir(outdated_path):
268 os.mkdir(outdated_path)
269 shutil.move(filename, "%s/%s" % (outdated_path, os.path.basename(filename)))
274 batch = len(sys.argv) != 2
276 print("Running in batch mode")
280 if not os.path.isfile(sys.argv[1]):
281 exit("file does not exists")
282 input_files.append(sys.argv[1])
284 file_pattern = re.compile(FILE_MATCH)
285 entries = os.listdir(WORKDIR)
286 for entry in entries:
287 path_entry = WORKDIR + "/" + entry
288 if not os.path.isfile(path_entry):
290 if not file_pattern.match(entry):
292 os.remove(path_entry)
294 if os.path.getsize(path_entry) > FILE_MAX_SIZE:
295 print("%s is too big" % entry)
296 os.remove(path_entry)
298 input_files.append(path_entry)
300 if not len(input_files):
301 exit("Nothing to process")
303 # Start processing each file
304 for input_file in input_files:
306 processFile(input_file)
307 except Exception as ex:
308 print(traceback.format_exc())