es_out: fix track reselection (fix #18543)
[vlc.git] / extras / misc / stackhandler.py
blob75e62c3a307239e459a51d6c31e643a245ddd243
1 #!/usr/bin/python
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 #####################################################################
35 VLC_VERSION = "2.0.3"
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}$"
41 FILE_MAX_SIZE = 10000
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"
47 EMAIL_BODY = \
48 """
49 Dear Bug Squasher,
51 This crash has been reported automatically and might be incomplete and/or broken.
52 Windows version: %(WIN32_VERSION)s
54 %(STACKTRACE)s
56 Truly yours,
57 a python script.
58 """
60 import os, sys, re, tempfile
61 import string, shlex, subprocess
62 import smtplib, datetime, shutil
63 import traceback
64 from email.mime.text import MIMEText
67 def processFile(filename):
68 print "Processing " + filename
69 global win32_version
71 f = open(filename, 'r')
72 # Read (and repair) the input file
73 content = "".join(filter(lambda x: x in string.printable, f.read()))
74 f.close()
76 if os.path.getsize(filename) < 10:
77 print("File empty")
78 os.remove(filename)
79 return
81 # Check if VLC version match
82 if not isValidVersion(content):
83 print("Invalid VLC version")
84 moveFile(filename, outdated = True)
85 return
87 # Get Windows version
88 win32_version = getWinVersion(content) or 'unknown'
90 # Map eip <--> library
91 mapping = mapLibraries(content)
92 if not mapping:
93 print("Stacktrace not found")
94 os.remove(filename)
95 return
97 # Associate all eip to their respective lib
98 # lib1
99 # `- 0x6904f020
100 # - 0x6927d37c
101 # lib2
102 # `- 0x7e418734
103 # - 0x7e418816
104 # - 0x7e42bf15
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)
110 # Send the email
111 sendEmail(body)
112 # Print the output
113 print(body)
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)
126 if res is not None:
127 return res.group(1)
128 return None
130 def getDiffAddress(content, name):
131 plugin_name_section = content.find(name)
132 if plugin_name_section < 0:
133 return None
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):
143 return None
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)
149 return diff
151 def mapLibraries(content):
152 stacktrace_section = content.find("[stacktrace]")
153 if stacktrace_section < 0:
154 return None
156 stacklines = content[stacktrace_section:]
157 stacklines = stacklines.splitlines()
158 pattern = re.compile(r"^([0-9a-fA-F]+)\|(.+)$")
160 mapping = []
161 for line in stacklines:
162 m = pattern.match(line)
163 print(line)
164 if m is not None:
165 mapping.append(m.group(1, 2))
167 if len(mapping) == 0:
168 return None
169 return mapping
172 def sortEIP(content, mapping):
173 # Merge all EIP mapping to the same library
174 libs = {}
175 libs_address = {}
176 for item in mapping:
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:
183 libs[libname] = []
184 diff = getDiffAddress(content, item[1])
185 if diff is not None:
186 libs_address[libname] = diff
187 else:
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):
196 eipmap = {}
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
204 for eip in v:
205 batchfile.write('p/a %s\n' % hex(eip))
206 batchfile.flush()
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)
215 # Parse result
216 gdb_pattern = re.compile(r"^\$\d+ = (.+)$")
217 cnt = 0
218 while p.poll() == None:
219 o = p.stdout.readline()
220 if o != b'':
221 o = bytes.decode(o)
222 m = gdb_pattern.match(o)
223 if m is not None:
224 #print("LINE: [%s]" % m.group(1))
225 eipmap[v[cnt]] = m.group(1)
226 cnt += 1
227 batchfile.close()
228 return eipmap
231 def genEmailBody(mapping, eipmap, delta_libs):
232 stacktrace = ""
233 cnt = 0
234 for item in mapping:
235 index = item[1].rfind('\\')
236 libname = item[1][index + 1:]
237 print(int(item[0],16), delta_libs[libname])
238 #print(eipmap)
239 #print(mapping)
240 stacktrace += "%d. %s [in %s]\n" % (cnt, eipmap[int(item[0],16)-delta_libs[libname]], item[1])
241 cnt += 1
242 stacktrace = stacktrace.rstrip('\n')
243 return EMAIL_BODY % {"STACKTRACE": stacktrace, "WIN32_VERSION": win32_version}
246 def sendEmail(body):
247 msg = MIMEText(body)
248 msg['Subject'] = EMAIL_SUBJECT
249 msg['From'] = EMAIL_FROM
250 msg['To'] = EMAIL_TO
252 # Send the email
253 s = smtplib.SMTP()
254 s.connect("127.0.0.1")
255 s.sendmail(EMAIL_FROM, [EMAIL_TO], msg.as_string())
256 s.quit()
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):
262 os.mkdir(today_path)
263 if not outdated:
264 shutil.move(filename, "%s/%s" % (today_path, os.path.basename(filename)))
265 else:
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)))
272 ### ENTRY POINT ###
274 batch = len(sys.argv) != 2
275 if batch:
276 print("Running in batch mode")
278 input_files = []
279 if not batch:
280 if not os.path.isfile(sys.argv[1]):
281 exit("file does not exists")
282 input_files.append(sys.argv[1])
283 else:
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):
289 continue
290 if not file_pattern.match(entry):
291 print(entry)
292 os.remove(path_entry)
293 continue
294 if os.path.getsize(path_entry) > FILE_MAX_SIZE:
295 print("%s is too big" % entry)
296 os.remove(path_entry)
297 continue
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:
305 try:
306 processFile(input_file)
307 except Exception as ex:
308 print(traceback.format_exc())