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())