aboutsummaryrefslogtreecommitdiffstats
path: root/debug_windows.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug_windows.py')
-rw-r--r--debug_windows.py360
1 files changed, 0 insertions, 360 deletions
diff --git a/debug_windows.py b/debug_windows.py
deleted file mode 100644
index ab1c03aba..000000000
--- a/debug_windows.py
+++ /dev/null
@@ -1,360 +0,0 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-###############
-
-"""
-This is a troubleshooting script that assists finding out which DLLs or
-which symbols in a DLL are missing when executing a PySide2 python
-script.
-It can also be used with any other non Python executable.
-
-Usage: python debug_windows.py
- When no arguments are given the script will try to import
- PySide2.QtCore.
-
-Usage: python debug_windows.py python -c "import PySide2.QtWebEngine"
- python debug_windows.py my_executable.exe arg1 arg2 --arg3=4
- Any arguments given after the script name will be considered
- as the target executable and the arguments passed to that
- executable.
-
-The script requires administrator privileges.
-
-The script uses cdb.exe and gflags.exe under the hood, which are
-installed together with the Windows Kit found at:
-https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
-
-"""
-
-from __future__ import print_function
-
-import sys
-import re
-import subprocess
-import ctypes
-import logging
-import argparse
-from os import path
-from textwrap import dedent
-
-is_win = sys.platform == "win32"
-is_py_3 = sys.version_info[0] == 3
-if is_win:
- if is_py_3:
- import winreg
- else:
- import _winreg as winreg
- import exceptions
-
-
-def get_parser_args():
- desc_msg = "Run an executable under cdb with loader snaps set."
- help_msg = "Pass the executable and the arguments passed to it as a list."
- parser = argparse.ArgumentParser(description=desc_msg)
- parser.add_argument('args', nargs='*', help=help_msg)
- # Prepend -- so that python options like '-c' are ignored by
- # argparse.
- massaged_args = ['--'] + sys.argv[1:]
- return parser.parse_args(massaged_args)
-
-
-parser_args = get_parser_args()
-verbose_log_file_name = path.join(path.dirname(path.abspath(__file__)),
- 'log_debug_windows.txt')
-
-
-def is_admin():
- try:
- return ctypes.windll.shell32.IsUserAnAdmin()
- except Exception as e:
- log.error("is_admin: Exception error: {}".format(e))
- return False
-
-
-def get_verbose_logger():
- handler = logging.FileHandler(verbose_log_file_name, mode='w')
- main_logger = logging.getLogger('main')
- main_logger.setLevel(logging.INFO)
- main_logger.addHandler(handler)
- return main_logger
-
-
-def get_non_verbose_logger():
- handler = logging.StreamHandler()
- main_logger = logging.getLogger('main.non_verbose')
- main_logger.setLevel(logging.INFO)
- main_logger.addHandler(handler)
- return main_logger
-
-
-big_log = get_verbose_logger()
-log = get_non_verbose_logger()
-
-
-def sub_keys(key):
- i = 0
- while True:
- try:
- sub_key = winreg.EnumKey(key, i)
- yield sub_key
- i += 1
- except WindowsError as e:
- log.error(e)
- break
-
-
-def sub_values(key):
- i = 0
- while True:
- try:
- v = winreg.EnumValue(key, i)
- yield v
- i += 1
- except WindowsError as e:
- log.error(e)
- break
-
-
-def get_installed_windows_kits():
- roots_key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"
- log.info("Searching for Windows kits in registry path: "
- "{}".format(roots_key))
- roots = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, roots_key, 0,
- winreg.KEY_READ)
- kits = []
- pattern = re.compile(r'KitsRoot(\d+)')
-
- for (name, value, value_type) in sub_values(roots):
- if value_type == winreg.REG_SZ and name.startswith('KitsRoot'):
- match = pattern.search(name)
- if match:
- version = match.group(1)
- kits.append({'version': version, 'value': value})
-
- if not kits:
- log.error(dedent("""
- No windows kits found in the registry.
- Consider downloading and installing the latest kit, either from
- https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools
- or from
- https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
- """))
- exit(1)
- return kits
-
-
-def get_appropriate_kit(kits):
- # Fixme, figure out if there is a more special way to choose a kit
- # and not just latest version.
- log.info("Found Windows kits are: {}".format(kits))
- chosen_kit = {'version': "0", 'value': None}
- for kit in kits:
- if (kit['version'] > chosen_kit['version'] and
- # version 8.1 is actually '81', so consider everything
- # above version 20, as '2.0', etc.
- kit['version'] < "20"):
- chosen_kit = kit
- first_kit = kits[0]
- return first_kit
-
-
-def get_cdb_and_gflags_path(kits):
- first_kit = get_appropriate_kit(kits)
- first_path_path = first_kit['value']
- log.info('Using kit found at {}'.format(first_path_path))
- bits = 'x64' if (sys.maxsize > 2 ** 32) else 'x32'
- debuggers_path = path.join(first_path_path, 'Debuggers', bits)
- cdb_path = path.join(debuggers_path, 'cdb.exe')
- if not path.exists(cdb_path): # Try for older "Debugging Tools" packages
- debuggers_path = "C:\\Program Files\\Debugging Tools for Windows (x64)"
- cdb_path = path.join(debuggers_path, 'cdb.exe')
-
- if not path.exists(cdb_path):
- log.error("Couldn't find cdb.exe at: {}.".format(cdb_path))
- exit(1)
- else:
- log.info("Found cdb.exe at: {}.".format(cdb_path))
-
- gflags_path = path.join(debuggers_path, 'gflags.exe')
-
- if not path.exists(gflags_path):
- log.error('Couldn\'t find gflags.exe at: {}.'.format(gflags_path))
- exit(1)
- else:
- log.info('Found gflags.exe at: {}.'.format(cdb_path))
-
- return cdb_path, gflags_path
-
-
-def toggle_loader_snaps(executable_name, gflags_path, enable=True):
- arg = '+sls' if enable else '-sls'
- gflags_args = [gflags_path, '-i', executable_name, arg]
- try:
- log.info('Invoking gflags: {}'.format(gflags_args))
- output = subprocess.check_output(gflags_args, stderr=subprocess.STDOUT,
- universal_newlines=True)
- log.info(output)
- except exceptions.WindowsError as e:
- log.error("\nRunning {} exited with exception: "
- "\n{}".format(gflags_args, e))
- exit(1)
- except subprocess.CalledProcessError as e:
- log.error("\nRunning {} exited with: {} and stdout was: "
- "{}".format(gflags_args, e.returncode, e.output))
- exit(1)
-
-
-def find_error_like_snippets(content):
- snippets = []
- lines = content.splitlines()
- context_lines = 4
-
- def error_predicate(l):
- # A list of mostly false positives are filtered out.
- # For deeper inspection, the full log exists.
- errors = {'errorhandling',
- 'windowserrorreporting',
- 'core-winrt-error',
- 'RtlSetLastWin32Error',
- 'RaiseInvalid16BitExeError',
- 'BaseWriteErrorElevationRequiredEvent',
- 'for DLL "Unknown"',
- 'LdrpGetProcedureAddress',
- 'X509_STORE_CTX_get_error',
- 'ERR_clear_error',
- 'ERR_peek_last_error',
- 'ERR_error_string',
- 'ERR_get_error',
- ('ERROR: Module load completed but symbols could '
- 'not be loaded')}
- return (re.search('error', l, re.IGNORECASE)
- and all(e not in errors for e in errors))
-
- for i in range(1, len(lines)):
- line = lines[i]
- if error_predicate(line):
- snippets.append(lines[i - context_lines:i + context_lines + 1])
-
- return snippets
-
-
-def print_error_snippets(snippets):
- if len(snippets) > 0:
- log.info("\nThe following possible errors were found:\n")
-
- for i in range(1, len(snippets)):
- log.info("Snippet {}:".format(i))
- for line in snippets[i]:
- log.info(line)
- log.info("")
-
-
-def call_command_under_cdb_with_gflags(executable_path, args):
- executable_name = path.basename(executable_path)
- invocation = [executable_path] + args
-
- kits = get_installed_windows_kits()
- cdb_path, gflags_path = get_cdb_and_gflags_path(kits)
-
- toggle_loader_snaps(executable_name, gflags_path, enable=True)
-
- log.info("Debugging the following command invocation: "
- "{}".format(invocation))
-
- cdb_args = [cdb_path] + invocation
-
- log.info('Invoking cdb: {}'.format(cdb_args))
-
- p = subprocess.Popen(cdb_args,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- stdin=subprocess.PIPE,
- shell=False)
-
- # Symbol fix, start process, print all thread stack traces, exit.
- cdb_commands = ['.symfix', 'g', '!uniqstack', 'q']
- cdb_commands_text = '\n'.join(cdb_commands)
- out, err = p.communicate(input=cdb_commands_text.encode('utf-8'))
-
- out_decoded = out.decode('utf-8')
- big_log.info('stdout: {}'.format(out_decoded))
- if err:
- big_log.info('stderr: {}'.format(err.decode('utf-8')))
-
- log.info('Finished execution of process under cdb.')
-
- toggle_loader_snaps(executable_name, gflags_path, enable=False)
-
- snippets = find_error_like_snippets(out_decoded)
- print_error_snippets(snippets)
-
- log.info("Finished processing.\n !!! Full log can be found at:\n"
- "{}".format(verbose_log_file_name))
-
-
-def test_run_import_qt_core_under_cdb_with_gflags():
- # The weird characters are there for faster grepping of the output
- # because there is a lot of content in the full log.
- # The 2+2 is just ensure that Python itself works.
- python_code = """
-print(">>>>>>>>>>>>>>>>>>>>>>> Test computation of 2+2 is: {}".format(2+2))
-import PySide2.QtCore
-print(">>>>>>>>>>>>>>>>>>>>>>> QtCore object instance: {}".format(PySide2.QtCore))
-"""
- call_command_under_cdb_with_gflags(sys.executable, ["-c", python_code])
-
-
-def handle_args():
- if not parser_args.args:
- test_run_import_qt_core_under_cdb_with_gflags()
- else:
- call_command_under_cdb_with_gflags(parser_args.args[0],
- parser_args.args[1:])
-
-
-if __name__ == '__main__':
- if not is_win:
- log.error("This script only works on Windows.")
- exit(1)
-
- if is_admin():
- handle_args()
- else:
- log.error("Please rerun the script with administrator privileges. "
- "It is required for gflags.exe to work. ")
- exit(1)