mirror of https://code.videolan.org/videolan/vlc
309 lines
9.3 KiB
Python
Executable File
309 lines
9.3 KiB
Python
Executable File
#!/usr/bin/python
|
|
#####################################################################
|
|
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
# Version 2, December 2004
|
|
#
|
|
# Copyright (C) 2011-2012 Ludovic Fauvet <etix@videolan.org>
|
|
# Jean-Baptiste Kempf <jb@videolan.org>
|
|
#
|
|
# Everyone is permitted to copy and distribute verbatim or modified
|
|
# copies of this license document, and changing it is allowed as long
|
|
# as the name is changed.
|
|
#
|
|
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
# TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
#
|
|
# 0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
#####################################################################
|
|
#
|
|
# This script can be started in two ways:
|
|
# - Without any arguments:
|
|
# The script will search for stacktrace in the WORKDIR, process
|
|
# them and dispatch them in their respective subdirectories.
|
|
# - With a stacktrace as only argument:
|
|
# The script will write the output on stdout and exit immediately
|
|
# after the stacktrace has been processed.
|
|
# The input file will stay in place, untouched.
|
|
#
|
|
# NOTE: Due to a bug in the mingw32-binutils > 2.19 the section
|
|
# .gnu_debuglink in the binary file is trimmed thus preventing
|
|
# gdb to find the associated symbols. This script will
|
|
# work around this issue and rerun gdb for each dbg file.
|
|
#
|
|
#####################################################################
|
|
|
|
VLC_VERSION = "2.0.3"
|
|
VLC_BIN = "/home/videolan/vlc/" + VLC_VERSION + "/vlc-" VLC_VERSION + "/vlc.exe"
|
|
VLC_BASE_DIR = "/home/videolan/vlc/" + VLC_VERSION + "/vlc-" + VLC_VERSION + "/"
|
|
VLC_SYMBOLS_DIR = "/home/videolan/vlc/" + VLC_VERSION + "/symbols-" + VLC_VERSION + "/"
|
|
WORKDIR = "/srv/ftp/crashes-win32"
|
|
FILE_MATCH = r"^\d{14}$"
|
|
FILE_MAX_SIZE = 10000
|
|
GDB_CMD = "gdb --exec=%(VLC_BIN)s --symbols=%(VLC_SYMBOLS_DIR)s%(DBG_FILE)s.dbg --batch -x %(BATCH_FILE)s"
|
|
|
|
EMAIL_TO = "bugreporter -- videolan.org"
|
|
EMAIL_FROM = "crashes@crash.videolan.org"
|
|
EMAIL_SUBJECT = "[CRASH] New Win32 crash report"
|
|
EMAIL_BODY = \
|
|
"""
|
|
Dear Bug Squasher,
|
|
|
|
This crash has been reported automatically and might be incomplete and/or broken.
|
|
Windows version: %(WIN32_VERSION)s
|
|
|
|
%(STACKTRACE)s
|
|
|
|
Truly yours,
|
|
a python script.
|
|
"""
|
|
|
|
import os, sys, re, tempfile
|
|
import string, shlex, subprocess
|
|
import smtplib, datetime, shutil
|
|
import traceback
|
|
from email.mime.text import MIMEText
|
|
|
|
|
|
def processFile(filename):
|
|
print "Processing " + filename
|
|
global win32_version
|
|
|
|
f = open(filename, 'r')
|
|
# Read (and repair) the input file
|
|
content = "".join(filter(lambda x: x in string.printable, f.read()))
|
|
f.close()
|
|
|
|
if os.path.getsize(filename) < 10:
|
|
print("File empty")
|
|
os.remove(filename)
|
|
return
|
|
|
|
# Check if VLC version match
|
|
if not isValidVersion(content):
|
|
print("Invalid VLC version")
|
|
moveFile(filename, outdated = True)
|
|
return
|
|
|
|
# Get Windows version
|
|
win32_version = getWinVersion(content) or 'unknown'
|
|
|
|
# Map eip <--> library
|
|
mapping = mapLibraries(content)
|
|
if not mapping:
|
|
print("Stacktrace not found")
|
|
os.remove(filename)
|
|
return
|
|
|
|
# Associate all eip to their respective lib
|
|
# lib1
|
|
# `- 0x6904f020
|
|
# - 0x6927d37c
|
|
# lib2
|
|
# `- 0x7e418734
|
|
# - 0x7e418816
|
|
# - 0x7e42bf15
|
|
sortedEIP,delta_libs = sortEIP(content,mapping)
|
|
# Compute the stacktrace using GDB
|
|
eipmap = findSymbols(sortedEIP)
|
|
# Generate the body of the email
|
|
body = genEmailBody(mapping, eipmap, delta_libs)
|
|
# Send the email
|
|
sendEmail(body)
|
|
# Print the output
|
|
print(body)
|
|
|
|
# Finally archive the stacktrace
|
|
moveFile(filename, outdated = False)
|
|
|
|
def isValidVersion(content):
|
|
pattern = re.compile(r"^VLC=%s " % VLC_VERSION, re.MULTILINE)
|
|
res = pattern.search(content)
|
|
return True if res else False
|
|
|
|
def getWinVersion(content):
|
|
pattern = re.compile(r"^OS=(.*)$", re.MULTILINE)
|
|
res = pattern.search(content)
|
|
if res is not None:
|
|
return res.group(1)
|
|
return None
|
|
|
|
def getDiffAddress(content, name):
|
|
plugin_name_section = content.find(name)
|
|
if plugin_name_section < 0:
|
|
return None
|
|
|
|
begin_index = content.rfind("\n", 0, plugin_name_section) + 1
|
|
end_index = content.find("|", begin_index)
|
|
|
|
tmp_index = name.rfind('plugins\\')
|
|
libname = name[tmp_index :].replace("\\", "/")
|
|
full_path = VLC_BASE_DIR + libname
|
|
|
|
if not os.path.isfile(full_path):
|
|
return None
|
|
|
|
cmd = "objdump -p " + full_path + " |grep ImageBase -|cut -f2-"
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().strip()
|
|
|
|
diff = int(content[begin_index:end_index], 16) - int(p, 16)
|
|
return diff
|
|
|
|
def mapLibraries(content):
|
|
stacktrace_section = content.find("[stacktrace]")
|
|
if stacktrace_section < 0:
|
|
return None
|
|
|
|
stacklines = content[stacktrace_section:]
|
|
stacklines = stacklines.splitlines()
|
|
pattern = re.compile(r"^([0-9a-fA-F]+)\|(.+)$")
|
|
|
|
mapping = []
|
|
for line in stacklines:
|
|
m = pattern.match(line)
|
|
print(line)
|
|
if m is not None:
|
|
mapping.append(m.group(1, 2))
|
|
|
|
if len(mapping) == 0:
|
|
return None
|
|
return mapping
|
|
|
|
|
|
def sortEIP(content, mapping):
|
|
# Merge all EIP mapping to the same library
|
|
libs = {}
|
|
libs_address = {}
|
|
for item in mapping:
|
|
# Extract the library name (without the full path)
|
|
index = item[1].rfind('\\')
|
|
libname = item[1][index + 1:]
|
|
|
|
# Append the eip to its respective lib
|
|
if libname not in libs:
|
|
libs[libname] = []
|
|
diff = getDiffAddress(content, item[1])
|
|
if diff is not None:
|
|
libs_address[libname] = diff
|
|
else:
|
|
libs_address[libname] = 0
|
|
|
|
libs[libname].append(int(item[0],16) - libs_address[libname])
|
|
|
|
return libs,libs_address
|
|
|
|
|
|
def findSymbols(sortedEIP):
|
|
eipmap = {}
|
|
|
|
for k, v in sortedEIP.items():
|
|
# Create the gdb batchfile
|
|
batchfile = tempfile.NamedTemporaryFile(mode="w")
|
|
batchfile.write("set print symbol-filename on\n")
|
|
|
|
# Append all eip for this lib
|
|
for eip in v:
|
|
batchfile.write('p/a %s\n' % hex(eip))
|
|
batchfile.flush()
|
|
|
|
# Generate the command line
|
|
cmd = GDB_CMD % {"VLC_BIN": VLC_BIN, "VLC_SYMBOLS_DIR": VLC_SYMBOLS_DIR, "DBG_FILE": k, "BATCH_FILE": batchfile.name}
|
|
args = shlex.split(cmd)
|
|
|
|
# Start GDB and get result
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
# Parse result
|
|
gdb_pattern = re.compile(r"^\$\d+ = (.+)$")
|
|
cnt = 0
|
|
while p.poll() == None:
|
|
o = p.stdout.readline()
|
|
if o != b'':
|
|
o = bytes.decode(o)
|
|
m = gdb_pattern.match(o)
|
|
if m is not None:
|
|
#print("LINE: [%s]" % m.group(1))
|
|
eipmap[v[cnt]] = m.group(1)
|
|
cnt += 1
|
|
batchfile.close()
|
|
return eipmap
|
|
|
|
|
|
def genEmailBody(mapping, eipmap, delta_libs):
|
|
stacktrace = ""
|
|
cnt = 0
|
|
for item in mapping:
|
|
index = item[1].rfind('\\')
|
|
libname = item[1][index + 1:]
|
|
print(int(item[0],16), delta_libs[libname])
|
|
#print(eipmap)
|
|
#print(mapping)
|
|
stacktrace += "%d. %s [in %s]\n" % (cnt, eipmap[int(item[0],16)-delta_libs[libname]], item[1])
|
|
cnt += 1
|
|
stacktrace = stacktrace.rstrip('\n')
|
|
return EMAIL_BODY % {"STACKTRACE": stacktrace, "WIN32_VERSION": win32_version}
|
|
|
|
|
|
def sendEmail(body):
|
|
msg = MIMEText(body)
|
|
msg['Subject'] = EMAIL_SUBJECT
|
|
msg['From'] = EMAIL_FROM
|
|
msg['To'] = EMAIL_TO
|
|
|
|
# Send the email
|
|
s = smtplib.SMTP()
|
|
s.connect("127.0.0.1")
|
|
s.sendmail(EMAIL_FROM, [EMAIL_TO], msg.as_string())
|
|
s.quit()
|
|
|
|
def moveFile(filename, outdated = False):
|
|
today = datetime.datetime.now().strftime("%Y%m%d")
|
|
today_path = "%s/%s" % (WORKDIR, today)
|
|
if not os.path.isdir(today_path):
|
|
os.mkdir(today_path)
|
|
if not outdated:
|
|
shutil.move(filename, "%s/%s" % (today_path, os.path.basename(filename)))
|
|
else:
|
|
outdated_path = "%s/outdated/" % today_path
|
|
if not os.path.isdir(outdated_path):
|
|
os.mkdir(outdated_path)
|
|
shutil.move(filename, "%s/%s" % (outdated_path, os.path.basename(filename)))
|
|
|
|
|
|
### ENTRY POINT ###
|
|
|
|
batch = len(sys.argv) != 2
|
|
if batch:
|
|
print("Running in batch mode")
|
|
|
|
input_files = []
|
|
if not batch:
|
|
if not os.path.isfile(sys.argv[1]):
|
|
exit("file does not exists")
|
|
input_files.append(sys.argv[1])
|
|
else:
|
|
file_pattern = re.compile(FILE_MATCH)
|
|
entries = os.listdir(WORKDIR)
|
|
for entry in entries:
|
|
path_entry = WORKDIR + "/" + entry
|
|
if not os.path.isfile(path_entry):
|
|
continue
|
|
if not file_pattern.match(entry):
|
|
print(entry)
|
|
os.remove(path_entry)
|
|
continue
|
|
if os.path.getsize(path_entry) > FILE_MAX_SIZE:
|
|
print("%s is too big" % entry)
|
|
os.remove(path_entry)
|
|
continue
|
|
input_files.append(path_entry)
|
|
|
|
if not len(input_files):
|
|
exit("Nothing to process")
|
|
|
|
# Start processing each file
|
|
for input_file in input_files:
|
|
try:
|
|
processFile(input_file)
|
|
except Exception as ex:
|
|
print(traceback.format_exc())
|