mirror of https://code.videolan.org/videolan/vlc
Stackhandler: process win32 stacks
This script was done by etix, and modified by me. Yes, this is not the most beautiful ever, but it works. Patches welcome
This commit is contained in:
parent
2a19f53cfe
commit
6738085c23
|
@ -0,0 +1,314 @@
|
|||
#!/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/2.0.3/vlc-2.0.3/vlc.exe"
|
||||
VLC_BASE_DIR = "/home/videolan/vlc/2.0.3/vlc-2.0.3/"
|
||||
VLC_SYMBOLS_DIR = "/home/videolan/vlc/2.0.3/symbols-2.0.3/"
|
||||
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 ###
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
print("Folder mode")
|
||||
batch = True
|
||||
if len(sys.argv) != 2:
|
||||
print("Running in batch mode")
|
||||
batch = True
|
||||
else:
|
||||
batch = False
|
||||
|
||||
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())
|
||||
|
Loading…
Reference in New Issue