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:
Jean-Baptiste Kempf 2012-09-07 14:44:48 +02:00
parent 2a19f53cfe
commit 6738085c23
1 changed files with 314 additions and 0 deletions

314
extras/misc/stackhandler.py Executable file
View File

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