diff options
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger')
23 files changed, 3219 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py new file mode 100644 index 00000000000..8013ceb6436 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py @@ -0,0 +1,227 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Base class for all debugger interface implementations.""" + +import abc +from itertools import chain +import os +import sys +import time +import traceback + +from dex.dextIR import DebuggerIR, ValueIR +from dex.utils.Exceptions import DebuggerException +from dex.utils.Exceptions import NotYetLoadedDebuggerException +from dex.utils.ReturnCode import ReturnCode + + +class DebuggerBase(object, metaclass=abc.ABCMeta): + def __init__(self, context, step_collection): + self.context = context + self.steps = step_collection + self._interface = None + self.has_loaded = False + self._loading_error = NotYetLoadedDebuggerException() + self.watches = set() + + try: + self._interface = self._load_interface() + self.has_loaded = True + self._loading_error = None + except DebuggerException: + self._loading_error = sys.exc_info() + + self.step_index = 0 + + def __enter__(self): + try: + self._custom_init() + self.clear_breakpoints() + self.add_breakpoints() + except DebuggerException: + self._loading_error = sys.exc_info() + return self + + def __exit__(self, *args): + self._custom_exit() + + def _custom_init(self): + pass + + def _custom_exit(self): + pass + + @property + def debugger_info(self): + return DebuggerIR(name=self.name, version=self.version) + + @property + def is_available(self): + return self.has_loaded and self.loading_error is None + + @property + def loading_error(self): + return (str(self._loading_error[1]) + if self._loading_error is not None else None) + + @property + def loading_error_trace(self): + if not self._loading_error: + return None + + tb = traceback.format_exception(*self._loading_error) + + if self._loading_error[1].orig_exception is not None: + orig_exception = traceback.format_exception( + *self._loading_error[1].orig_exception) + + if ''.join(orig_exception) not in ''.join(tb): + tb.extend(['\n']) + tb.extend(orig_exception) + + tb = ''.join(tb).splitlines(True) + return tb + + def add_breakpoints(self): + for s in self.context.options.source_files: + with open(s, 'r') as fp: + num_lines = len(fp.readlines()) + for line in range(1, num_lines + 1): + self.add_breakpoint(s, line) + + def _update_step_watches(self, step_info): + loc = step_info.current_location + watch_cmds = ['DexUnreachable', 'DexExpectStepOrder'] + towatch = chain.from_iterable(self.steps.commands[x] + for x in watch_cmds + if x in self.steps.commands) + try: + # Iterate over all watches of the types named in watch_cmds + for watch in towatch: + if (os.path.exists(loc.path) + and os.path.samefile(watch.path, loc.path) + and watch.lineno == loc.lineno): + result = watch.eval(self) + step_info.watches.update(result) + break + except KeyError: + pass + + def _sanitize_function_name(self, name): # pylint: disable=no-self-use + """If the function name returned by the debugger needs any post- + processing to make it fit (for example, if it includes a byte offset), + do that here. + """ + return name + + def start(self): + self.steps.clear_steps() + self.launch() + + for command_obj in chain.from_iterable(self.steps.commands.values()): + self.watches.update(command_obj.get_watches()) + + max_steps = self.context.options.max_steps + for _ in range(max_steps): + while self.is_running: + pass + + if self.is_finished: + break + + self.step_index += 1 + step_info = self.get_step_info() + + if step_info.current_frame: + self._update_step_watches(step_info) + self.steps.new_step(self.context, step_info) + + if self.in_source_file(step_info): + self.step() + else: + self.go() + + time.sleep(self.context.options.pause_between_steps) + else: + raise DebuggerException( + 'maximum number of steps reached ({})'.format(max_steps)) + + def in_source_file(self, step_info): + if not step_info.current_frame: + return False + if not os.path.exists(step_info.current_location.path): + return False + return any(os.path.samefile(step_info.current_location.path, f) \ + for f in self.context.options.source_files) + + @abc.abstractmethod + def _load_interface(self): + pass + + @classmethod + def get_option_name(cls): + """Short name that will be used on the command line to specify this + debugger. + """ + raise NotImplementedError() + + @classmethod + def get_name(cls): + """Full name of this debugger.""" + raise NotImplementedError() + + @property + def name(self): + return self.__class__.get_name() + + @property + def option_name(self): + return self.__class__.get_option_name() + + @abc.abstractproperty + def version(self): + pass + + @abc.abstractmethod + def clear_breakpoints(self): + pass + + @abc.abstractmethod + def add_breakpoint(self, file_, line): + pass + + @abc.abstractmethod + def launch(self): + pass + + @abc.abstractmethod + def step(self): + pass + + @abc.abstractmethod + def go(self) -> ReturnCode: + pass + + @abc.abstractmethod + def get_step_info(self): + pass + + @abc.abstractproperty + def is_running(self): + pass + + @abc.abstractproperty + def is_finished(self): + pass + + @abc.abstractproperty + def frames_below_main(self): + pass + + @abc.abstractmethod + def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: + pass diff --git a/debuginfo-tests/dexter/dex/debugger/Debuggers.py b/debuginfo-tests/dexter/dex/debugger/Debuggers.py new file mode 100644 index 00000000000..2f246a3fd95 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/Debuggers.py @@ -0,0 +1,299 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Discover potential/available debugger interfaces.""" + +from collections import OrderedDict +import os +import pickle +import subprocess +import sys +from tempfile import NamedTemporaryFile + +from dex.command import find_all_commands +from dex.dextIR import DextIR +from dex.utils import get_root_directory, Timer +from dex.utils.Environment import is_native_windows +from dex.utils.Exceptions import CommandParseError, DebuggerException +from dex.utils.Exceptions import ToolArgumentError +from dex.utils.Warning import warn + +from dex.debugger.dbgeng.dbgeng import DbgEng +from dex.debugger.lldb.LLDB import LLDB +from dex.debugger.visualstudio.VisualStudio2015 import VisualStudio2015 +from dex.debugger.visualstudio.VisualStudio2017 import VisualStudio2017 + + +def _get_potential_debuggers(): # noqa + """Return a dict of the supported debuggers. + Returns: + { name (str): debugger (class) } + """ + return { + DbgEng.get_option_name(): DbgEng, + LLDB.get_option_name(): LLDB, + VisualStudio2015.get_option_name(): VisualStudio2015, + VisualStudio2017.get_option_name(): VisualStudio2017 + } + + +def _warn_meaningless_option(context, option): + if context.options.list_debuggers: + return + + warn(context, + 'option <y>"{}"</> is meaningless with this debugger'.format(option), + '--debugger={}'.format(context.options.debugger)) + + +def add_debugger_tool_base_arguments(parser, defaults): + defaults.lldb_executable = 'lldb.exe' if is_native_windows() else 'lldb' + parser.add_argument( + '--lldb-executable', + type=str, + metavar='<file>', + default=None, + display_default=defaults.lldb_executable, + help='location of LLDB executable') + + +def add_debugger_tool_arguments(parser, context, defaults): + debuggers = Debuggers(context) + potential_debuggers = sorted(debuggers.potential_debuggers().keys()) + + add_debugger_tool_base_arguments(parser, defaults) + + parser.add_argument( + '--debugger', + type=str, + choices=potential_debuggers, + required=True, + help='debugger to use') + parser.add_argument( + '--max-steps', + metavar='<int>', + type=int, + default=1000, + help='maximum number of program steps allowed') + parser.add_argument( + '--pause-between-steps', + metavar='<seconds>', + type=float, + default=0.0, + help='number of seconds to pause between steps') + defaults.show_debugger = False + parser.add_argument( + '--show-debugger', + action='store_true', + default=None, + help='show the debugger') + defaults.arch = 'x86_64' + parser.add_argument( + '--arch', + type=str, + metavar='<architecture>', + default=None, + display_default=defaults.arch, + help='target architecture') + + +def handle_debugger_tool_base_options(context, defaults): # noqa + options = context.options + + if options.lldb_executable is None: + options.lldb_executable = defaults.lldb_executable + else: + if getattr(options, 'debugger', 'lldb') != 'lldb': + _warn_meaningless_option(context, '--lldb-executable') + + options.lldb_executable = os.path.abspath(options.lldb_executable) + if not os.path.isfile(options.lldb_executable): + raise ToolArgumentError('<d>could not find</> <r>"{}"</>'.format( + options.lldb_executable)) + + +def handle_debugger_tool_options(context, defaults): # noqa + options = context.options + + handle_debugger_tool_base_options(context, defaults) + + if options.arch is None: + options.arch = defaults.arch + else: + if options.debugger != 'lldb': + _warn_meaningless_option(context, '--arch') + + if options.show_debugger is None: + options.show_debugger = defaults.show_debugger + else: + if options.debugger == 'lldb': + _warn_meaningless_option(context, '--show-debugger') + + +def _get_command_infos(context): + commands = find_all_commands(context.options.source_files) + command_infos = OrderedDict() + for command_type in commands: + for command in commands[command_type].values(): + if command_type not in command_infos: + command_infos[command_type] = [] + command_infos[command_type].append(command) + return OrderedDict(command_infos) + + +def empty_debugger_steps(context): + return DextIR( + executable_path=context.options.executable, + source_paths=context.options.source_files, + dexter_version=context.version) + + +def get_debugger_steps(context): + step_collection = empty_debugger_steps(context) + + with Timer('parsing commands'): + try: + step_collection.commands = _get_command_infos(context) + except CommandParseError as e: + msg = 'parser error: <d>{}({}):</> {}\n{}\n{}\n'.format( + e.filename, e.lineno, e.info, e.src, e.caret) + raise DebuggerException(msg) + + with NamedTemporaryFile( + dir=context.working_directory.path, delete=False) as fp: + pickle.dump(step_collection, fp, protocol=pickle.HIGHEST_PROTOCOL) + steps_path = fp.name + + with NamedTemporaryFile( + dir=context.working_directory.path, delete=False, mode='wb') as fp: + pickle.dump(context.options, fp, protocol=pickle.HIGHEST_PROTOCOL) + options_path = fp.name + + dexter_py = os.path.basename(sys.argv[0]) + if not os.path.isfile(dexter_py): + dexter_py = os.path.join(get_root_directory(), '..', dexter_py) + assert os.path.isfile(dexter_py) + + with NamedTemporaryFile(dir=context.working_directory.path) as fp: + args = [ + sys.executable, dexter_py, 'run-debugger-internal-', steps_path, + options_path, '--working-directory', context.working_directory.path, + '--unittest=off', '--indent-timer-level={}'.format(Timer.indent + 2) + ] + try: + with Timer('running external debugger process'): + subprocess.check_call(args) + except subprocess.CalledProcessError as e: + raise DebuggerException(e) + + with open(steps_path, 'rb') as fp: + step_collection = pickle.load(fp) + + return step_collection + + +class Debuggers(object): + @classmethod + def potential_debuggers(cls): + try: + return cls._potential_debuggers + except AttributeError: + cls._potential_debuggers = _get_potential_debuggers() + return cls._potential_debuggers + + def __init__(self, context): + self.context = context + + def load(self, key, step_collection=None): + with Timer('load {}'.format(key)): + return Debuggers.potential_debuggers()[key](self.context, + step_collection) + + def _populate_debugger_cache(self): + debuggers = [] + for key in sorted(Debuggers.potential_debuggers()): + debugger = self.load(key) + + class LoadedDebugger(object): + pass + + LoadedDebugger.option_name = key + LoadedDebugger.full_name = '[{}]'.format(debugger.name) + LoadedDebugger.is_available = debugger.is_available + + if LoadedDebugger.is_available: + try: + LoadedDebugger.version = debugger.version.splitlines() + except AttributeError: + LoadedDebugger.version = [''] + else: + try: + LoadedDebugger.error = debugger.loading_error.splitlines() + except AttributeError: + LoadedDebugger.error = [''] + + try: + LoadedDebugger.error_trace = debugger.loading_error_trace + except AttributeError: + LoadedDebugger.error_trace = None + + debuggers.append(LoadedDebugger) + return debuggers + + def list(self): + debuggers = self._populate_debugger_cache() + + max_o_len = max(len(d.option_name) for d in debuggers) + max_n_len = max(len(d.full_name) for d in debuggers) + + msgs = [] + + for d in debuggers: + # Option name, right padded with spaces for alignment + option_name = ( + '{{name: <{}}}'.format(max_o_len).format(name=d.option_name)) + + # Full name, right padded with spaces for alignment + full_name = ('{{name: <{}}}'.format(max_n_len) + .format(name=d.full_name)) + + if d.is_available: + name = '<b>{} {}</>'.format(option_name, full_name) + + # If the debugger is available, show the first line of the + # version info. + available = '<g>YES</>' + info = '<b>({})</>'.format(d.version[0]) + else: + name = '<y>{} {}</>'.format(option_name, full_name) + + # If the debugger is not available, show the first line of the + # error reason. + available = '<r>NO</> ' + info = '<y>({})</>'.format(d.error[0]) + + msg = '{} {} {}'.format(name, available, info) + + if self.context.options.verbose: + # If verbose mode and there was more version or error output + # than could be displayed in a single line, display the whole + # lot slightly indented. + verbose_info = None + if d.is_available: + if d.version[1:]: + verbose_info = d.version + ['\n'] + else: + # Some of list elems may contain multiple lines, so make + # sure each elem is a line of its own. + verbose_info = d.error_trace + + if verbose_info: + verbose_info = '\n'.join(' {}'.format(l.rstrip()) + for l in verbose_info) + '\n' + msg = '{}\n\n{}'.format(msg, verbose_info) + + msgs.append(msg) + self.context.o.auto('\n{}\n\n'.format('\n'.join(msgs))) diff --git a/debuginfo-tests/dexter/dex/debugger/__init__.py b/debuginfo-tests/dexter/dex/debugger/__init__.py new file mode 100644 index 00000000000..3c4fdece479 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/__init__.py @@ -0,0 +1,8 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from dex.debugger.Debuggers import Debuggers diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md new file mode 100644 index 00000000000..f9b864206d3 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/README.md @@ -0,0 +1,60 @@ +# Debugger Engine backend + +This directory contains the Dexter backend for the Windows Debugger Engine +(DbgEng), which powers tools such as WinDbg and CDB. + +## Overview + +DbgEng is available as a collection of unregistered COM-"like" objects that +one accesses by calling DebugCreate in DbgEng.dll. The unregistered nature +means normal COM tooling can't access them; as a result, this backend uses +ctypes to describe the COM objects and call their methods. + +This is obviously not a huge amount of fun; on the other hand, COM has +maintained ABI compatible interfaces for decades, and nothing is for free. + +The dexter backend follows the same formula as others; it creates a process +and breaks on "main", then steps through the program, observing states and +stack frames along the way. + +## Implementation details + +This backend uses a mixture of both APIs for accessing information, and the +direct command-string interface to DbgEng for performing some actions. We +have to use the DbgEng stepping interface, or we would effectively be +building a new debugger, but certain things (like enabling source-line +stepping) only seem to be possible from the command interface. + +Each segment of debugger responsibility has its own COM object: Client, +Control, Symbols, SymbolGroups, Breakpoint, SystemObjects. In this python +wrapper, each COM object gets a python object wrapping it. COM methods +that are relevant to our interests have a python method that wraps the COM +one and performs data marshalling. Some additional helper methods are added +to the python objects to extract data. + +The majority of the work occurs in setup.py and probe_process.py. The +former contains routines to launch a process and attach the debugger to +it, while the latter extracts as much information as possible from a +stopped process, returning a list of stack frames with associated variable +information. + +## Sharp edges + +For reasons still unclear, using CreateProcessAndAttach never appears to +allow the debuggee to resume, hence this implementation creates the +debuggee process manually, attaches, and resumes. + +On process startup, we set a breakpoint on main and then continue running +to it. This has the potential to never complete -- although of course, +there's no guarantee that the debuggee will ever do anything anyway. + +There doesn't appear to be a way to instruct DbgEng to "step into" a +function call, thus after reaching main, we scan the module for all +functions with line numbers in the source directory, and put breakpoints +on them. An alternative implementation would be putting breakpoints on +every known line number. + +Finally, it's unclear whether arbitrary expressions can be evaluated in +arbitrary stack frames, although this isn't something that Dexter currently +supports. + diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/__init__.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/__init__.py new file mode 100644 index 00000000000..3c458f955b7 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/__init__.py @@ -0,0 +1,19 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from . import dbgeng + +import platform +if platform.system() == 'Windows': + from . import breakpoint + from . import control + from . import probe_process + from . import setup + from . import symbols + from . import symgroup + from . import sysobjs + from . import utils diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/breakpoint.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/breakpoint.py new file mode 100644 index 00000000000..c966d8c9c88 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/breakpoint.py @@ -0,0 +1,88 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * +from enum import * +from functools import partial + +from .utils import * + +class BreakpointTypes(IntEnum): + DEBUG_BREAKPOINT_CODE = 0 + DEBUG_BREAKPOINT_DATA = 1 + DEBUG_BREAKPOINT_TIME = 2 + DEBUG_BREAKPOINT_INLINE = 3 + +class BreakpointFlags(IntFlag): + DEBUG_BREAKPOINT_GO_ONLY = 0x00000001 + DEBUG_BREAKPOINT_DEFERRED = 0x00000002 + DEBUG_BREAKPOINT_ENABLED = 0x00000004 + DEBUG_BREAKPOINT_ADDER_ONLY = 0x00000008 + DEBUG_BREAKPOINT_ONE_SHOT = 0x00000010 + +DebugBreakpoint2IID = IID(0x1b278d20, 0x79f2, 0x426e, IID_Data4_Type(0xa3, 0xf9, 0xc1, 0xdd, 0xf3, 0x75, 0xd4, 0x8e)) + +class DebugBreakpoint2(Structure): + pass + +class DebugBreakpoint2Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(DebugBreakpoint2)) + idb_setoffset = wrp(c_ulonglong) + idb_setflags = wrp(c_ulong) + _fields_ = [ + ("QueryInterface", c_void_p), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("GetId", c_void_p), + ("GetType", c_void_p), + ("GetAdder", c_void_p), + ("GetFlags", c_void_p), + ("AddFlags", c_void_p), + ("RemoveFlags", c_void_p), + ("SetFlags", idb_setflags), + ("GetOffset", c_void_p), + ("SetOffset", idb_setoffset), + ("GetDataParameters", c_void_p), + ("SetDataParameters", c_void_p), + ("GetPassCount", c_void_p), + ("SetPassCount", c_void_p), + ("GetCurrentPassCount", c_void_p), + ("GetMatchThreadId", c_void_p), + ("SetMatchThreadId", c_void_p), + ("GetCommand", c_void_p), + ("SetCommand", c_void_p), + ("GetOffsetExpression", c_void_p), + ("SetOffsetExpression", c_void_p), + ("GetParameters", c_void_p), + ("GetCommandWide", c_void_p), + ("SetCommandWide", c_void_p), + ("GetOffsetExpressionWide", c_void_p), + ("SetOffsetExpressionWide", c_void_p) + ] + +DebugBreakpoint2._fields_ = [("lpVtbl", POINTER(DebugBreakpoint2Vtbl))] + +class Breakpoint(object): + def __init__(self, breakpoint): + self.breakpoint = breakpoint.contents + self.vt = self.breakpoint.lpVtbl.contents + + def SetFlags(self, flags): + res = self.vt.SetFlags(self.breakpoint, flags) + aborter(res, "Breakpoint SetFlags") + + def SetOffset(self, offs): + res = self.vt.SetOffset(self.breakpoint, offs) + aborter(res, "Breakpoint SetOffset") + + def RemoveFlags(self, flags): + res = self.vt.RemoveFlags(self.breakpoint, flags) + aborter(res, "Breakpoint RemoveFlags") + + def die(self): + self.breakpoint = None + self.vt = None diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py new file mode 100644 index 00000000000..a65e4ded2f3 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/client.py @@ -0,0 +1,185 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * +from enum import * +from functools import partial + +from .utils import * +from . import control +from . import symbols +from . import sysobjs + +class DebugAttach(IntFlag): + DEBUG_ATTACH_DEFAULT = 0 + DEBUG_ATTACH_NONINVASIVE = 1 + DEBUG_ATTACH_EXISTING = 2 + DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND = 4 + DEBUG_ATTACH_INVASIVE_NO_INITIAL_BREAK = 8 + DEBUG_ATTACH_INVASIVE_RESUME_PROCESS = 0x10 + DEBUG_ATTACH_NONINVASIVE_ALLOW_PARTIAL = 0x20 + +# UUID for DebugClient7 interface. +DebugClient7IID = IID(0x13586be3, 0x542e, 0x481e, IID_Data4_Type(0xb1, 0xf2, 0x84, 0x97, 0xba, 0x74, 0xf9, 0xa9 )) + +class IDebugClient7(Structure): + pass + +class IDebugClient7Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugClient7)) + idc_queryinterface = wrp(POINTER(IID), POINTER(c_void_p)) + idc_attachprocess = wrp(c_longlong, c_long, c_long) + idc_detachprocesses = wrp() + _fields_ = [ + ("QueryInterface", idc_queryinterface), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("AttachKernel", c_void_p), + ("GetKernelConnectionOptions", c_void_p), + ("SetKernelConnectionOptions", c_void_p), + ("StartProcessServer", c_void_p), + ("ConnectProcessServer", c_void_p), + ("DisconnectProcessServer", c_void_p), + ("GetRunningProcessSystemIds", c_void_p), + ("GetRunningProcessSystemIdsByExecutableName", c_void_p), + ("GetRunningProcessDescription", c_void_p), + ("AttachProcess", idc_attachprocess), + ("CreateProcess", c_void_p), + ("CreateProcessAndAttach", c_void_p), + ("GetProcessOptions", c_void_p), + ("AddProcessOptions", c_void_p), + ("RemoveProcessOptions", c_void_p), + ("SetProcessOptions", c_void_p), + ("OpenDumpFile", c_void_p), + ("WriteDumpFile", c_void_p), + ("ConnectSession", c_void_p), + ("StartServer", c_void_p), + ("OutputServers", c_void_p), + ("TerminateProcesses", c_void_p), + ("DetachProcesses", idc_detachprocesses), + ("EndSession", c_void_p), + ("GetExitCode", c_void_p), + ("DispatchCallbacks", c_void_p), + ("ExitDispatch", c_void_p), + ("CreateClient", c_void_p), + ("GetInputCallbacks", c_void_p), + ("SetInputCallbacks", c_void_p), + ("GetOutputCallbacks", c_void_p), + ("SetOutputCallbacks", c_void_p), + ("GetOutputMask", c_void_p), + ("SetOutputMask", c_void_p), + ("GetOtherOutputMask", c_void_p), + ("SetOtherOutputMask", c_void_p), + ("GetOutputWidth", c_void_p), + ("SetOutputWidth", c_void_p), + ("GetOutputLinePrefix", c_void_p), + ("SetOutputLinePrefix", c_void_p), + ("GetIdentity", c_void_p), + ("OutputIdentity", c_void_p), + ("GetEventCallbacks", c_void_p), + ("SetEventCallbacks", c_void_p), + ("FlushCallbacks", c_void_p), + ("WriteDumpFile2", c_void_p), + ("AddDumpInformationFile", c_void_p), + ("EndProcessServer", c_void_p), + ("WaitForProcessServerEnd", c_void_p), + ("IsKernelDebuggerEnabled", c_void_p), + ("TerminateCurrentProcess", c_void_p), + ("DetachCurrentProcess", c_void_p), + ("AbandonCurrentProcess", c_void_p), + ("GetRunningProcessSystemIdByExecutableNameWide", c_void_p), + ("GetRunningProcessDescriptionWide", c_void_p), + ("CreateProcessWide", c_void_p), + ("CreateProcessAndAttachWide", c_void_p), + ("OpenDumpFileWide", c_void_p), + ("WriteDumpFileWide", c_void_p), + ("AddDumpInformationFileWide", c_void_p), + ("GetNumberDumpFiles", c_void_p), + ("GetDumpFile", c_void_p), + ("GetDumpFileWide", c_void_p), + ("AttachKernelWide", c_void_p), + ("GetKernelConnectionOptionsWide", c_void_p), + ("SetKernelConnectionOptionsWide", c_void_p), + ("StartProcessServerWide", c_void_p), + ("ConnectProcessServerWide", c_void_p), + ("StartServerWide", c_void_p), + ("OutputServerWide", c_void_p), + ("GetOutputCallbacksWide", c_void_p), + ("SetOutputCallbacksWide", c_void_p), + ("GetOutputLinePrefixWide", c_void_p), + ("SetOutputLinePrefixWide", c_void_p), + ("GetIdentityWide", c_void_p), + ("OutputIdentityWide", c_void_p), + ("GetEventCallbacksWide", c_void_p), + ("SetEventCallbacksWide", c_void_p), + ("CreateProcess2", c_void_p), + ("CreateProcess2Wide", c_void_p), + ("CreateProcessAndAttach2", c_void_p), + ("CreateProcessAndAttach2Wide", c_void_p), + ("PushOutputLinePrefix", c_void_p), + ("PushOutputLinePrefixWide", c_void_p), + ("PopOutputLinePrefix", c_void_p), + ("GetNumberInputCallbacks", c_void_p), + ("GetNumberOutputCallbacks", c_void_p), + ("GetNumberEventCallbacks", c_void_p), + ("GetQuitLockString", c_void_p), + ("SetQuitLockString", c_void_p), + ("GetQuitLockStringWide", c_void_p), + ("SetQuitLockStringWide", c_void_p), + ("SetEventContextCallbacks", c_void_p), + ("SetClientContext", c_void_p), + ] + +IDebugClient7._fields_ = [("lpVtbl", POINTER(IDebugClient7Vtbl))] + +class Client(object): + def __init__(self): + DbgEng = WinDLL("DbgEng") + DbgEng.DebugCreate.argtypes = [POINTER(IID), POINTER(POINTER(IDebugClient7))] + DbgEng.DebugCreate.restype = c_ulong + + # Call DebugCreate to create a new debug client + ptr = POINTER(IDebugClient7)() + res = DbgEng.DebugCreate(byref(DebugClient7IID), ptr) + aborter(res, "DebugCreate") + self.client = ptr.contents + self.vt = vt = self.client.lpVtbl.contents + + def QI(iface, ptr): + return vt.QueryInterface(self.client, byref(iface), byref(ptr)) + + # Query for a control object + ptr = c_void_p() + res = QI(control.DebugControl7IID, ptr) + aborter(res, "QueryInterface control") + self.control_ptr = cast(ptr, POINTER(control.IDebugControl7)) + self.Control = control.Control(self.control_ptr) + + # Query for a SystemObjects object + ptr = c_void_p() + res = QI(sysobjs.DebugSystemObjects4IID, ptr) + aborter(res, "QueryInterface sysobjects") + self.sysobjects_ptr = cast(ptr, POINTER(sysobjs.IDebugSystemObjects4)) + self.SysObjects = sysobjs.SysObjects(self.sysobjects_ptr) + + # Query for a Symbols object + ptr = c_void_p() + res = QI(symbols.DebugSymbols5IID, ptr) + aborter(res, "QueryInterface debugsymbosl5") + self.symbols_ptr = cast(ptr, POINTER(symbols.IDebugSymbols5)) + self.Symbols = symbols.Symbols(self.symbols_ptr) + + def AttachProcess(self, pid): + # Zero process-server id means no process-server. + res = self.vt.AttachProcess(self.client, 0, pid, DebugAttach.DEBUG_ATTACH_DEFAULT) + aborter(res, "AttachProcess") + return + + def DetachProcesses(self): + res = self.vt.DetachProcesses(self.client) + aborter(res, "DetachProcesses") + return diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py new file mode 100644 index 00000000000..38585c83f70 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/control.py @@ -0,0 +1,405 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * +from functools import partial + +from .utils import * +from .breakpoint import * + +class DEBUG_STACK_FRAME_EX(Structure): + _fields_ = [ + ("InstructionOffset", c_ulonglong), + ("ReturnOffset", c_ulonglong), + ("FrameOffset", c_ulonglong), + ("StackOffset", c_ulonglong), + ("FuncTableEntry", c_ulonglong), + ("Params", c_ulonglong * 4), + ("Reserved", c_ulonglong * 6), + ("Virtual", c_bool), + ("FrameNumber", c_ulong), + ("InlineFrameContext", c_ulong), + ("Reserved1", c_ulong) + ] +PDEBUG_STACK_FRAME_EX = POINTER(DEBUG_STACK_FRAME_EX) + +class DEBUG_VALUE_U(Union): + _fields_ = [ + ("I8", c_byte), + ("I16", c_short), + ("I32", c_int), + ("I64", c_long), + ("F32", c_float), + ("F64", c_double), + ("RawBytes", c_ubyte * 24) # Force length to 24b. + ] + +class DEBUG_VALUE(Structure): + _fields_ = [ + ("U", DEBUG_VALUE_U), + ("TailOfRawBytes", c_ulong), + ("Type", c_ulong) + ] +PDEBUG_VALUE = POINTER(DEBUG_VALUE) + +class DebugValueType(IntEnum): + DEBUG_VALUE_INVALID = 0 + DEBUG_VALUE_INT8 = 1 + DEBUG_VALUE_INT16 = 2 + DEBUG_VALUE_INT32 = 3 + DEBUG_VALUE_INT64 = 4 + DEBUG_VALUE_FLOAT32 = 5 + DEBUG_VALUE_FLOAT64 = 6 + DEBUG_VALUE_FLOAT80 = 7 + DEBUG_VALUE_FLOAT82 = 8 + DEBUG_VALUE_FLOAT128 = 9 + DEBUG_VALUE_VECTOR64 = 10 + DEBUG_VALUE_VECTOR128 = 11 + DEBUG_VALUE_TYPES = 12 + +# UUID for DebugControl7 interface. +DebugControl7IID = IID(0xb86fb3b1, 0x80d4, 0x475b, IID_Data4_Type(0xae, 0xa3, 0xcf, 0x06, 0x53, 0x9c, 0xf6, 0x3a)) + +class IDebugControl7(Structure): + pass + +class IDebugControl7Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugControl7)) + idc_getnumbereventfilters = wrp(c_ulong_p, c_ulong_p, c_ulong_p) + idc_setexceptionfiltersecondcommand = wrp(c_ulong, c_char_p) + idc_waitforevent = wrp(c_long, c_long) + idc_execute = wrp(c_long, c_char_p, c_long) + idc_setexpressionsyntax = wrp(c_ulong) + idc_addbreakpoint2 = wrp(c_ulong, c_ulong, POINTER(POINTER(DebugBreakpoint2))) + idc_setexecutionstatus = wrp(c_ulong) + idc_getexecutionstatus = wrp(c_ulong_p) + idc_getstacktraceex = wrp(c_ulonglong, c_ulonglong, c_ulonglong, PDEBUG_STACK_FRAME_EX, c_ulong, c_ulong_p) + idc_evaluate = wrp(c_char_p, c_ulong, PDEBUG_VALUE, c_ulong_p) + _fields_ = [ + ("QueryInterface", c_void_p), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("GetInterrupt", c_void_p), + ("SetInterrupt", c_void_p), + ("GetInterruptTimeout", c_void_p), + ("SetInterruptTimeout", c_void_p), + ("GetLogFile", c_void_p), + ("OpenLogFile", c_void_p), + ("CloseLogFile", c_void_p), + ("GetLogMask", c_void_p), + ("SetLogMask", c_void_p), + ("Input", c_void_p), + ("ReturnInput", c_void_p), + ("Output", c_void_p), + ("OutputVaList", c_void_p), + ("ControlledOutput", c_void_p), + ("ControlledOutputVaList", c_void_p), + ("OutputPrompt", c_void_p), + ("OutputPromptVaList", c_void_p), + ("GetPromptText", c_void_p), + ("OutputCurrentState", c_void_p), + ("OutputVersionInformation", c_void_p), + ("GetNotifyEventHandle", c_void_p), + ("SetNotifyEventHandle", c_void_p), + ("Assemble", c_void_p), + ("Disassemble", c_void_p), + ("GetDisassembleEffectiveOffset", c_void_p), + ("OutputDisassembly", c_void_p), + ("OutputDisassemblyLines", c_void_p), + ("GetNearInstruction", c_void_p), + ("GetStackTrace", c_void_p), + ("GetReturnOffset", c_void_p), + ("OutputStackTrace", c_void_p), + ("GetDebuggeeType", c_void_p), + ("GetActualProcessorType", c_void_p), + ("GetExecutingProcessorType", c_void_p), + ("GetNumberPossibleExecutingProcessorTypes", c_void_p), + ("GetPossibleExecutingProcessorTypes", c_void_p), + ("GetNumberProcessors", c_void_p), + ("GetSystemVersion", c_void_p), + ("GetPageSize", c_void_p), + ("IsPointer64Bit", c_void_p), + ("ReadBugCheckData", c_void_p), + ("GetNumberSupportedProcessorTypes", c_void_p), + ("GetSupportedProcessorTypes", c_void_p), + ("GetProcessorTypeNames", c_void_p), + ("GetEffectiveProcessorType", c_void_p), + ("SetEffectiveProcessorType", c_void_p), + ("GetExecutionStatus", idc_getexecutionstatus), + ("SetExecutionStatus", idc_setexecutionstatus), + ("GetCodeLevel", c_void_p), + ("SetCodeLevel", c_void_p), + ("GetEngineOptions", c_void_p), + ("AddEngineOptions", c_void_p), + ("RemoveEngineOptions", c_void_p), + ("SetEngineOptions", c_void_p), + ("GetSystemErrorControl", c_void_p), + ("SetSystemErrorControl", c_void_p), + ("GetTextMacro", c_void_p), + ("SetTextMacro", c_void_p), + ("GetRadix", c_void_p), + ("SetRadix", c_void_p), + ("Evaluate", idc_evaluate), + ("CoerceValue", c_void_p), + ("CoerceValues", c_void_p), + ("Execute", idc_execute), + ("ExecuteCommandFile", c_void_p), + ("GetNumberBreakpoints", c_void_p), + ("GetBreakpointByIndex", c_void_p), + ("GetBreakpointById", c_void_p), + ("GetBreakpointParameters", c_void_p), + ("AddBreakpoint", c_void_p), + ("RemoveBreakpoint", c_void_p), + ("AddExtension", c_void_p), + ("RemoveExtension", c_void_p), + ("GetExtensionByPath", c_void_p), + ("CallExtension", c_void_p), + ("GetExtensionFunction", c_void_p), + ("GetWindbgExtensionApis32", c_void_p), + ("GetWindbgExtensionApis64", c_void_p), + ("GetNumberEventFilters", idc_getnumbereventfilters), + ("GetEventFilterText", c_void_p), + ("GetEventFilterCommand", c_void_p), + ("SetEventFilterCommand", c_void_p), + ("GetSpecificFilterParameters", c_void_p), + ("SetSpecificFilterParameters", c_void_p), + ("GetSpecificFilterArgument", c_void_p), + ("SetSpecificFilterArgument", c_void_p), + ("GetExceptionFilterParameters", c_void_p), + ("SetExceptionFilterParameters", c_void_p), + ("GetExceptionFilterSecondCommand", c_void_p), + ("SetExceptionFilterSecondCommand", idc_setexceptionfiltersecondcommand), + ("WaitForEvent", idc_waitforevent), + ("GetLastEventInformation", c_void_p), + ("GetCurrentTimeDate", c_void_p), + ("GetCurrentSystemUpTime", c_void_p), + ("GetDumpFormatFlags", c_void_p), + ("GetNumberTextReplacements", c_void_p), + ("GetTextReplacement", c_void_p), + ("SetTextReplacement", c_void_p), + ("RemoveTextReplacements", c_void_p), + ("OutputTextReplacements", c_void_p), + ("GetAssemblyOptions", c_void_p), + ("AddAssemblyOptions", c_void_p), + ("RemoveAssemblyOptions", c_void_p), + ("SetAssemblyOptions", c_void_p), + ("GetExpressionSyntax", c_void_p), + ("SetExpressionSyntax", idc_setexpressionsyntax), + ("SetExpressionSyntaxByName", c_void_p), + ("GetNumberExpressionSyntaxes", c_void_p), + ("GetExpressionSyntaxNames", c_void_p), + ("GetNumberEvents", c_void_p), + ("GetEventIndexDescription", c_void_p), + ("GetCurrentEventIndex", c_void_p), + ("SetNextEventIndex", c_void_p), + ("GetLogFileWide", c_void_p), + ("OpenLogFileWide", c_void_p), + ("InputWide", c_void_p), + ("ReturnInputWide", c_void_p), + ("OutputWide", c_void_p), + ("OutputVaListWide", c_void_p), + ("ControlledOutputWide", c_void_p), + ("ControlledOutputVaListWide", c_void_p), + ("OutputPromptWide", c_void_p), + ("OutputPromptVaListWide", c_void_p), + ("GetPromptTextWide", c_void_p), + ("AssembleWide", c_void_p), + ("DisassembleWide", c_void_p), + ("GetProcessrTypeNamesWide", c_void_p), + ("GetTextMacroWide", c_void_p), + ("SetTextMacroWide", c_void_p), + ("EvaluateWide", c_void_p), + ("ExecuteWide", c_void_p), + ("ExecuteCommandFileWide", c_void_p), + ("GetBreakpointByIndex2", c_void_p), + ("GetBreakpointById2", c_void_p), + ("AddBreakpoint2", idc_addbreakpoint2), + ("RemoveBreakpoint2", c_void_p), + ("AddExtensionWide", c_void_p), + ("GetExtensionByPathWide", c_void_p), + ("CallExtensionWide", c_void_p), + ("GetExtensionFunctionWide", c_void_p), + ("GetEventFilterTextWide", c_void_p), + ("GetEventfilterCommandWide", c_void_p), + ("SetEventFilterCommandWide", c_void_p), + ("GetSpecificFilterArgumentWide", c_void_p), + ("SetSpecificFilterArgumentWide", c_void_p), + ("GetExceptionFilterSecondCommandWide", c_void_p), + ("SetExceptionFilterSecondCommandWider", c_void_p), + ("GetLastEventInformationWide", c_void_p), + ("GetTextReplacementWide", c_void_p), + ("SetTextReplacementWide", c_void_p), + ("SetExpressionSyntaxByNameWide", c_void_p), + ("GetExpressionSyntaxNamesWide", c_void_p), + ("GetEventIndexDescriptionWide", c_void_p), + ("GetLogFile2", c_void_p), + ("OpenLogFile2", c_void_p), + ("GetLogFile2Wide", c_void_p), + ("OpenLogFile2Wide", c_void_p), + ("GetSystemVersionValues", c_void_p), + ("GetSystemVersionString", c_void_p), + ("GetSystemVersionStringWide", c_void_p), + ("GetContextStackTrace", c_void_p), + ("OutputContextStackTrace", c_void_p), + ("GetStoredEventInformation", c_void_p), + ("GetManagedStatus", c_void_p), + ("GetManagedStatusWide", c_void_p), + ("ResetManagedStatus", c_void_p), + ("GetStackTraceEx", idc_getstacktraceex), + ("OutputStackTraceEx", c_void_p), + ("GetContextStackTraceEx", c_void_p), + ("OutputContextStackTraceEx", c_void_p), + ("GetBreakpointByGuid", c_void_p), + ("GetExecutionStatusEx", c_void_p), + ("GetSynchronizationStatus", c_void_p), + ("GetDebuggeeType2", c_void_p) + ] + +IDebugControl7._fields_ = [("lpVtbl", POINTER(IDebugControl7Vtbl))] + +class DebugStatus(IntEnum): + DEBUG_STATUS_NO_CHANGE = 0 + DEBUG_STATUS_GO = 1 + DEBUG_STATUS_GO_HANDLED = 2 + DEBUG_STATUS_GO_NOT_HANDLED = 3 + DEBUG_STATUS_STEP_OVER = 4 + DEBUG_STATUS_STEP_INTO = 5 + DEBUG_STATUS_BREAK = 6 + DEBUG_STATUS_NO_DEBUGGEE = 7 + DEBUG_STATUS_STEP_BRANCH = 8 + DEBUG_STATUS_IGNORE_EVENT = 9 + DEBUG_STATUS_RESTART_REQUESTED = 10 + DEBUG_STATUS_REVERSE_GO = 11 + DEBUG_STATUS_REVERSE_STEP_BRANCH = 12 + DEBUG_STATUS_REVERSE_STEP_OVER = 13 + DEBUG_STATUS_REVERSE_STEP_INTO = 14 + DEBUG_STATUS_OUT_OF_SYNC = 15 + DEBUG_STATUS_WAIT_INPUT = 16 + DEBUG_STATUS_TIMEOUT = 17 + +class DebugSyntax(IntEnum): + DEBUG_EXPR_MASM = 0 + DEBUG_EXPR_CPLUSPLUS = 1 + +class Control(object): + def __init__(self, control): + self.ptr = control + self.control = control.contents + self.vt = self.control.lpVtbl.contents + # Keep a handy ulong for passing into C methods. + self.ulong = c_ulong() + + def GetExecutionStatus(self, doprint=False): + ret = self.vt.GetExecutionStatus(self.control, byref(self.ulong)) + aborter(ret, "GetExecutionStatus") + status = DebugStatus(self.ulong.value) + if doprint: + print("Execution status: {}".format(status)) + return status + + def SetExecutionStatus(self, status): + assert isinstance(status, DebugStatus) + res = self.vt.SetExecutionStatus(self.control, status.value) + aborter(res, "SetExecutionStatus") + + def WaitForEvent(self, timeout=100): + # No flags are taken by WaitForEvent, hence 0 + ret = self.vt.WaitForEvent(self.control, 0, timeout) + aborter(ret, "WaitforEvent", ignore=[S_FALSE]) + return ret + + def GetNumberEventFilters(self): + specific_events = c_ulong() + specific_exceptions = c_ulong() + arbitrary_exceptions = c_ulong() + res = self.vt.GetNumberEventFilters(self.control, byref(specific_events), + byref(specific_exceptions), + byref(arbitrary_exceptions)) + aborter(res, "GetNumberEventFilters") + return (specific_events.value, specific_exceptions.value, + arbitrary_exceptions.value) + + def SetExceptionFilterSecondCommand(self, index, command): + buf = create_string_buffer(command.encode('ascii')) + res = self.vt.SetExceptionFilterSecondCommand(self.control, index, buf) + aborter(res, "SetExceptionFilterSecondCommand") + return + + def AddBreakpoint2(self, offset=None, enabled=None): + breakpoint = POINTER(DebugBreakpoint2)() + res = self.vt.AddBreakpoint2(self.control, BreakpointTypes.DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, byref(breakpoint)) + aborter(res, "Add breakpoint 2") + bp = Breakpoint(breakpoint) + + if offset is not None: + bp.SetOffset(offset) + if enabled is not None and enabled: + bp.SetFlags(BreakpointFlags.DEBUG_BREAKPOINT_ENABLED) + + return bp + + def RemoveBreakpoint(self, bp): + res = self.vt.RemoveBreakpoint2(self.control, bp.breakpoint) + aborter(res, "RemoveBreakpoint2") + bp.die() + + def GetStackTraceEx(self): + # XXX -- I can't find a way to query for how many stack frames there _are_ + # in advance. Guess 128 for now. + num_frames_buffer = 128 + + frames = (DEBUG_STACK_FRAME_EX * num_frames_buffer)() + numframes = c_ulong() + + # First three args are frame/stack/IP offsets -- leave them as zero to + # default to the current instruction. + res = self.vt.GetStackTraceEx(self.control, 0, 0, 0, frames, num_frames_buffer, byref(numframes)) + aborter(res, "GetStackTraceEx") + return frames, numframes.value + + def Execute(self, command): + # First zero is DEBUG_OUTCTL_*, which we leave as a default, second + # zero is DEBUG_EXECUTE_* flags, of which we set none. + res = self.vt.Execute(self.control, 0, command.encode('ascii'), 0) + aborter(res, "Client execute") + + def SetExpressionSyntax(self, cpp=True): + if cpp: + syntax = DebugSyntax.DEBUG_EXPR_CPLUSPLUS + else: + syntax = DebugSyntax.DEBUG_EXPR_MASM + + res = self.vt.SetExpressionSyntax(self.control, syntax) + aborter(res, "SetExpressionSyntax") + + def Evaluate(self, expr): + ptr = DEBUG_VALUE() + res = self.vt.Evaluate(self.control, expr.encode("ascii"), DebugValueType.DEBUG_VALUE_INVALID, byref(ptr), None) + aborter(res, "Evaluate", ignore=[E_INTERNALEXCEPTION, E_FAIL]) + if res != 0: + return None + + val_type = DebugValueType(ptr.Type) + + # Here's a map from debug value types to fields. Unclear what happens + # with unsigned values, as DbgEng doesn't present any unsigned fields. + + extract_map = { + DebugValueType.DEBUG_VALUE_INT8 : ("I8", "char"), + DebugValueType.DEBUG_VALUE_INT16 : ("I16", "short"), + DebugValueType.DEBUG_VALUE_INT32 : ("I32", "int"), + DebugValueType.DEBUG_VALUE_INT64 : ("I64", "long"), + DebugValueType.DEBUG_VALUE_FLOAT32 : ("F32", "float"), + DebugValueType.DEBUG_VALUE_FLOAT64 : ("F64", "double") + } # And everything else is invalid. + + if val_type not in extract_map: + raise Exception("Unexpected debug value type {} when evalutaing".format(val_type)) + + # Also produce a type name... + + return getattr(ptr.U, extract_map[val_type][0]), extract_map[val_type][1] diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py new file mode 100644 index 00000000000..66d01f03e8f --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py @@ -0,0 +1,163 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import sys +import os +import platform + +from dex.debugger.DebuggerBase import DebuggerBase +from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR +from dex.dextIR import ProgramState, StackFrame, SourceLocation +from dex.utils.Exceptions import DebuggerException, LoadDebuggerException +from dex.utils.ReturnCode import ReturnCode + +if platform.system() == "Windows": + # Don't load on linux; _load_interface will croak before any names are used. + from . import setup + from . import probe_process + from . import breakpoint + +class DbgEng(DebuggerBase): + def __init__(self, context, *args): + self.breakpoints = [] + self.running = False + self.finished = False + self.step_info = None + super(DbgEng, self).__init__(context, *args) + + def _custom_init(self): + try: + res = setup.setup_everything(self.context.options.executable) + self.client, self.hProcess = res + self.running = True + except Exception as e: + raise Exception('Failed to start debuggee: {}'.format(e)) + + def _custom_exit(self): + setup.cleanup(self.client, self.hProcess) + + def _load_interface(self): + arch = platform.architecture()[0] + machine = platform.machine() + if arch == '32bit' and machine == 'AMD64': + # This python process is 32 bits, but is sitting on a 64 bit machine. + # Bad things may happen, don't support it. + raise LoadDebuggerException('Can\'t run Dexter dbgeng on 32 bit python in a 64 bit environment') + + if platform.system() != 'Windows': + raise LoadDebuggerException('DbgEng supports Windows only') + + # Otherwise, everything was imported earlier + + @classmethod + def get_name(cls): + return 'dbgeng' + + @classmethod + def get_option_name(cls): + return 'dbgeng' + + @property + def frames_below_main(self): + return [] + + @property + def version(self): + # I don't believe there's a well defined DbgEng version, outside of the + # version of Windows being used. + return "1" + + def clear_breakpoints(self): + for x in self.breakpoints: + x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED) + self.client.Control.RemoveBreakpoint(x) + + def add_breakpoint(self, file_, line): + # This is something to implement in the future -- as it stands, Dexter + # doesn't test for such things as "I can set a breakpoint on this line". + # This is only called AFAICT right now to ensure we break on every step. + pass + + def launch(self): + # We are, by this point, already launched. + self.step_info = probe_process.probe_state(self.client) + + def step(self): + res = setup.step_once(self.client) + if not res: + self.finished = True + self.step_info = res + + def go(self): + # We never go -- we always single step. + pass + + def get_step_info(self): + frames = self.step_info + state_frames = [] + + # For now assume the base function is the... function, ignoring + # inlining. + dex_frames = [] + for i, x in enumerate(frames): + # XXX Might be able to get columns out through + # GetSourceEntriesByOffset, not a priority now + loc = LocIR(path=x.source_file, lineno=x.line_no, column=0) + new_frame = FrameIR(function=x.function_name, is_inlined=False, loc=loc) + dex_frames.append(new_frame) + + state_frame = StackFrame(function=new_frame.function, + is_inlined=new_frame.is_inlined, + location=SourceLocation(path=x.source_file, + lineno=x.line_no, + column=0), + watches={}) + for expr in map( + lambda watch, idx=i: self.evaluate_expression(watch, idx), + self.watches): + state_frame.watches[expr.expression] = expr + state_frames.append(state_frame) + + return StepIR( + step_index=self.step_index, frames=dex_frames, + stop_reason=StopReason.STEP, + program_state=ProgramState(state_frames)) + + @property + def is_running(self): + return False # We're never free-running + + @property + def is_finished(self): + return self.finished + + def evaluate_expression(self, expression, frame_idx=0): + # XXX: cdb insists on using '->' to examine fields of structures, + # as it appears to reserve '.' for other purposes. + fixed_expr = expression.replace('.', '->') + + orig_scope_idx = self.client.Symbols.GetCurrentScopeFrameIndex() + self.client.Symbols.SetScopeFrameByIndex(frame_idx) + + res = self.client.Control.Evaluate(fixed_expr) + if res is not None: + result, typename = self.client.Control.Evaluate(fixed_expr) + could_eval = True + else: + result, typename = (None, None) + could_eval = False + + self.client.Symbols.SetScopeFrameByIndex(orig_scope_idx) + + return ValueIR( + expression=expression, + value=str(result), + type_name=typename, + error_string="", + could_evaluate=could_eval, + is_optimized_away=False, + is_irretrievable=not could_eval) diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/probe_process.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/probe_process.py new file mode 100644 index 00000000000..8bd7f607081 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/probe_process.py @@ -0,0 +1,80 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import os + +from .utils import * + +class Frame(object): + def __init__(self, frame, idx, Symbols): + # Store some base information about the frame + self.ip = frame.InstructionOffset + self.scope_idx = idx + self.virtual = frame.Virtual + self.inline_frame_context = frame.InlineFrameContext + self.func_tbl_entry = frame.FuncTableEntry + + # Fetch the module/symbol we're in, with displacement. Useful for debugging. + self.descr = Symbols.GetNearNameByOffset(self.ip) + split = self.descr.split('!')[0] + self.module = split[0] + self.symbol = split[1] + + # Fetch symbol group for this scope. + prevscope = Symbols.GetCurrentScopeFrameIndex() + if Symbols.SetScopeFrameByIndex(idx): + symgroup = Symbols.GetScopeSymbolGroup2() + Symbols.SetScopeFrameByIndex(prevscope) + self.symgroup = symgroup + else: + self.symgroup = None + + # Fetch the name according to the line-table, using inlining context. + name = Symbols.GetNameByInlineContext(self.ip, self.inline_frame_context) + self.function_name = name.split('!')[-1] + + try: + tup = Symbols.GetLineByInlineContext(self.ip, self.inline_frame_context) + self.source_file, self.line_no = tup + except WinError as e: + # Fall back to trying to use a non-inlining-aware line number + # XXX - this is not inlining aware + sym = Symbols.GetLineByOffset(self.ip) + if sym is not None: + self.source_file, self.line_no = sym + else: + self.source_file = None + self.line_no = None + self.basename = None + + if self.source_file is not None: + self.basename = os.path.basename(self.source_file) + else: + self.basename = None + + + + def __str__(self): + return '{}:{}({}) {}'.format(self.basename, self.line, self.descr, self.function_name) + +def main_on_stack(Symbols, frames): + module_name = Symbols.get_exefile_module_name() + main_name = "{}!main".format(module_name) + for x in frames: + if main_name in x.descr: # Could be less hard coded... + return True + return False + +def probe_state(Client): + # Fetch the state of the program -- represented by the stack frames. + frames, numframes = Client.Control.GetStackTraceEx() + + the_frames = [Frame(frames[x], x, Client.Symbols) for x in range(numframes)] + if not main_on_stack(Client.Symbols, the_frames): + return None + + return the_frames diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py new file mode 100644 index 00000000000..30a62f6dd42 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py @@ -0,0 +1,185 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * + +from . import client +from . import control +from . import symbols +from .probe_process import probe_state +from .utils import * + +class STARTUPINFOA(Structure): + _fields_ = [ + ('cb', c_ulong), + ('lpReserved', c_char_p), + ('lpDesktop', c_char_p), + ('lpTitle', c_char_p), + ('dwX', c_ulong), + ('dwY', c_ulong), + ('dwXSize', c_ulong), + ('dwYSize', c_ulong), + ('dwXCountChars', c_ulong), + ('dwYCountChars', c_ulong), + ('dwFillAttribute', c_ulong), + ('wShowWindow', c_ushort), + ('cbReserved2', c_ushort), + ('lpReserved2', c_char_p), + ('hStdInput', c_void_p), + ('hStdOutput', c_void_p), + ('hStdError', c_void_p) + ] + +class PROCESS_INFORMATION(Structure): + _fields_ = [ + ('hProcess', c_void_p), + ('hThread', c_void_p), + ('dwProcessId', c_ulong), + ('dwThreadId', c_ulong) + ] + +def fetch_local_function_syms(Symbols, prefix): + syms = Symbols.get_all_functions() + + def is_sym_in_src_dir(sym): + name, data = sym + symdata = Symbols.GetLineByOffset(data.Offset) + if symdata is not None: + srcfile, line = symdata + if prefix in srcfile: + return True + return False + + syms = [x for x in syms if is_sym_in_src_dir(x)] + return syms + +def break_on_all_but_main(Control, Symbols, main_offset): + mainfile, _ = Symbols.GetLineByOffset(main_offset) + prefix = '\\'.join(mainfile.split('\\')[:-1]) + + for name, rec in fetch_local_function_syms(Symbols, prefix): + if name == "main": + continue + bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True) + + # All breakpoints are currently discarded: we just sys.exit for cleanup + return + +def process_creator(binfile): + Kernel32 = WinDLL("Kernel32") + + # Another flavour of process creation + startupinfoa = STARTUPINFOA() + startupinfoa.cb = sizeof(STARTUPINFOA) + startupinfoa.lpReserved = None + startupinfoa.lpDesktop = None + startupinfoa.lpTitle = None + startupinfoa.dwX = 0 + startupinfoa.dwY = 0 + startupinfoa.dwXSize = 0 + startupinfoa.dwYSize = 0 + startupinfoa.dwXCountChars = 0 + startupinfoa.dwYCountChars = 0 + startupinfoa.dwFillAttribute = 0 + startupinfoa.dwFlags = 0 + startupinfoa.wShowWindow = 0 + startupinfoa.cbReserved2 = 0 + startupinfoa.lpReserved2 = None + startupinfoa.hStdInput = None + startupinfoa.hStdOutput = None + startupinfoa.hStdError = None + processinformation = PROCESS_INFORMATION() + + # 0x4 below specifies CREATE_SUSPENDED. + ret = Kernel32.CreateProcessA(binfile.encode("ascii"), None, None, None, False, 0x4, None, None, byref(startupinfoa), byref(processinformation)) + if ret == 0: + raise Exception('CreateProcess running {}'.format(binfile)) + + return processinformation.dwProcessId, processinformation.dwThreadId, processinformation.hProcess, processinformation.hThread + +def thread_resumer(hProcess, hThread): + Kernel32 = WinDLL("Kernel32") + + # For reasons unclear to me, other suspend-references seem to be opened on + # the opened thread. Clear them all. + while True: + ret = Kernel32.ResumeThread(hThread) + if ret <= 0: + break + if ret < 0: + Kernel32.TerminateProcess(hProcess, 1) + raise Exception("Couldn't resume process after startup") + + return + +def setup_everything(binfile): + from . import client + from . import symbols + Client = client.Client() + + created_pid, created_tid, hProcess, hThread = process_creator(binfile) + + # Load lines as well as general symbols + sym_opts = Client.Symbols.GetSymbolOptions() + sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES + Client.Symbols.SetSymbolOptions(sym_opts) + + Client.AttachProcess(created_pid) + + # Need to enter the debugger engine to let it attach properly + Client.Control.WaitForEvent(timeout=1) + Client.SysObjects.set_current_thread(created_pid, created_tid) + Client.Control.Execute("l+t") + Client.Control.SetExpressionSyntax(cpp=True) + + module_name = Client.Symbols.get_exefile_module_name() + offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name)) + breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True) + thread_resumer(hProcess, hThread) + Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO) + + # Problem: there is no guarantee that the client will ever reach main, + # something else exciting could happen in that time, the host system may + # be very loaded, and similar. Wait for some period, say, five seconds, and + # abort afterwards: this is a trade-off between spurious timeouts and + # completely hanging in the case of a environmental/programming error. + res = Client.Control.WaitForEvent(timeout=5000) + if res == S_FALSE: + Kernel32.TerminateProcess(hProcess, 1) + raise Exception("Debuggee did not reach main function in a timely manner") + + break_on_all_but_main(Client.Control, Client.Symbols, offset) + + # Set the default action on all exceptions to be "quit and detach". If we + # don't, dbgeng will merrily spin at the exception site forever. + filts = Client.Control.GetNumberEventFilters() + for x in range(filts[0], filts[0] + filts[1]): + Client.Control.SetExceptionFilterSecondCommand(x, "qd") + + return Client, hProcess + +def step_once(client): + client.Control.Execute("p") + try: + client.Control.WaitForEvent() + except Exception as e: + if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE: + return None # Debuggee has gone away, likely due to an exception. + raise e + # Could assert here that we're in the "break" state + client.Control.GetExecutionStatus() + return probe_state(client) + +def main_loop(client): + res = True + while res is not None: + res = step_once(client) + +def cleanup(client, hProcess): + res = client.DetachProcesses() + Kernel32 = WinDLL("Kernel32") + Kernel32.TerminateProcess(hProcess, 1) diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/symbols.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/symbols.py new file mode 100644 index 00000000000..bc998facb4e --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/symbols.py @@ -0,0 +1,499 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from collections import namedtuple +from ctypes import * +from enum import * +from functools import reduce, partial + +from .symgroup import SymbolGroup, IDebugSymbolGroup2 +from .utils import * + +class SymbolOptionFlags(IntFlag): + SYMOPT_CASE_INSENSITIVE = 0x00000001 + SYMOPT_UNDNAME = 0x00000002 + SYMOPT_DEFERRED_LOADS = 0x00000004 + SYMOPT_NO_CPP = 0x00000008 + SYMOPT_LOAD_LINES = 0x00000010 + SYMOPT_OMAP_FIND_NEAREST = 0x00000020 + SYMOPT_LOAD_ANYTHING = 0x00000040 + SYMOPT_IGNORE_CVREC = 0x00000080 + SYMOPT_NO_UNQUALIFIED_LOADS = 0x00000100 + SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200 + SYMOPT_EXACT_SYMBOLS = 0x00000400 + SYMOPT_ALLOW_ABSOLUTE_SYMBOLS = 0x00000800 + SYMOPT_IGNORE_NT_SYMPATH = 0x00001000 + SYMOPT_INCLUDE_32BIT_MODULES = 0x00002000 + SYMOPT_PUBLICS_ONLY = 0x00004000 + SYMOPT_NO_PUBLICS = 0x00008000 + SYMOPT_AUTO_PUBLICS = 0x00010000 + SYMOPT_NO_IMAGE_SEARCH = 0x00020000 + SYMOPT_SECURE = 0x00040000 + SYMOPT_NO_PROMPTS = 0x00080000 + SYMOPT_DEBUG = 0x80000000 + +class ScopeGroupFlags(IntFlag): + DEBUG_SCOPE_GROUP_ARGUMENTS = 0x00000001 + DEBUG_SCOPE_GROUP_LOCALS = 0x00000002 + DEBUG_SCOPE_GROUP_ALL = 0x00000003 + DEBUG_SCOPE_GROUP_BY_DATAMODEL = 0x00000004 + +class DebugModuleNames(IntEnum): + DEBUG_MODNAME_IMAGE = 0x00000000 + DEBUG_MODNAME_MODULE = 0x00000001 + DEBUG_MODNAME_LOADED_IMAGE = 0x00000002 + DEBUG_MODNAME_SYMBOL_FILE = 0x00000003 + DEBUG_MODNAME_MAPPED_IMAGE = 0x00000004 + +class DebugModuleFlags(IntFlag): + DEBUG_MODULE_LOADED = 0x00000000 + DEBUG_MODULE_UNLOADED = 0x00000001 + DEBUG_MODULE_USER_MODE = 0x00000002 + DEBUG_MODULE_EXE_MODULE = 0x00000004 + DEBUG_MODULE_EXPLICIT = 0x00000008 + DEBUG_MODULE_SECONDARY = 0x00000010 + DEBUG_MODULE_SYNTHETIC = 0x00000020 + DEBUG_MODULE_SYM_BAD_CHECKSUM = 0x00010000 + +class DEBUG_MODULE_PARAMETERS(Structure): + _fields_ = [ + ("Base", c_ulonglong), + ("Size", c_ulong), + ("TimeDateStamp", c_ulong), + ("Checksum", c_ulong), + ("Flags", c_ulong), + ("SymbolType", c_ulong), + ("ImageNameSize", c_ulong), + ("ModuleNameSize", c_ulong), + ("LoadedImageNameSize", c_ulong), + ("SymbolFileNameSize", c_ulong), + ("MappedImageNameSize", c_ulong), + ("Reserved", c_ulonglong * 2) + ] +PDEBUG_MODULE_PARAMETERS = POINTER(DEBUG_MODULE_PARAMETERS) + +class DEBUG_MODULE_AND_ID(Structure): + _fields_ = [ + ("ModuleBase", c_ulonglong), + ("Id", c_ulonglong) + ] +PDEBUG_MODULE_AND_ID = POINTER(DEBUG_MODULE_AND_ID) + +class DEBUG_SYMBOL_ENTRY(Structure): + _fields_ = [ + ("ModuleBase", c_ulonglong), + ("Offset", c_ulonglong), + ("Id", c_ulonglong), + ("Arg64", c_ulonglong), + ("Size", c_ulong), + ("Flags", c_ulong), + ("TypeId", c_ulong), + ("NameSize", c_ulong), + ("Token", c_ulong), + ("Tag", c_ulong), + ("Arg32", c_ulong), + ("Reserved", c_ulong) + ] +PDEBUG_SYMBOL_ENTRY = POINTER(DEBUG_SYMBOL_ENTRY) + +# UUID for DebugSymbols5 interface. +DebugSymbols5IID = IID(0xc65fa83e, 0x1e69, 0x475e, IID_Data4_Type(0x8e, 0x0e, 0xb5, 0xd7, 0x9e, 0x9c, 0xc1, 0x7e)) + +class IDebugSymbols5(Structure): + pass + +class IDebugSymbols5Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSymbols5)) + ids_getsymboloptions = wrp(c_ulong_p) + ids_setsymboloptions = wrp(c_ulong) + ids_getmoduleparameters = wrp(c_ulong, c_ulong64_p, c_ulong, PDEBUG_MODULE_PARAMETERS) + ids_getmodulenamestring = wrp(c_ulong, c_ulong, c_ulonglong, c_char_p, c_ulong, c_ulong_p) + ids_getoffsetbyname = wrp(c_char_p, c_ulong64_p) + ids_getlinebyoffset = wrp(c_ulonglong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p) + ids_getsymbolentriesbyname = wrp(c_char_p, c_ulong, PDEBUG_MODULE_AND_ID, c_ulong, c_ulong_p) + ids_getsymbolentrystring = wrp(PDEBUG_MODULE_AND_ID, c_ulong, c_char_p, c_ulong, c_ulong_p) + ids_getsymbolentryinformation = wrp(PDEBUG_MODULE_AND_ID, PDEBUG_SYMBOL_ENTRY) + ids_getcurrentscopeframeindex = wrp(c_ulong_p) + ids_getnearnamebyoffset = wrp(c_ulonglong, c_long, c_char_p, c_ulong, c_ulong_p, c_ulong64_p) + ids_setscopeframebyindex = wrp(c_ulong) + ids_getscopesymbolgroup2 = wrp(c_ulong, POINTER(IDebugSymbolGroup2), POINTER(POINTER(IDebugSymbolGroup2))) + ids_getnamebyinlinecontext = wrp(c_ulonglong, c_ulong, c_char_p, c_ulong, c_ulong_p, c_ulong64_p) + ids_getlinebyinlinecontext = wrp(c_ulonglong, c_ulong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p) + _fields_ = [ + ("QueryInterface", c_void_p), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("GetSymbolOptions", ids_getsymboloptions), + ("AddSymbolOptions", c_void_p), + ("RemoveSymbolOptions", c_void_p), + ("SetSymbolOptions", ids_setsymboloptions), + ("GetNameByOffset", c_void_p), + ("GetOffsetByName", ids_getoffsetbyname), + ("GetNearNameByOffset", ids_getnearnamebyoffset), + ("GetLineByOffset", ids_getlinebyoffset), + ("GetOffsetByLine", c_void_p), + ("GetNumberModules", c_void_p), + ("GetModuleByIndex", c_void_p), + ("GetModuleByModuleName", c_void_p), + ("GetModuleByOffset", c_void_p), + ("GetModuleNames", c_void_p), + ("GetModuleParameters", ids_getmoduleparameters), + ("GetSymbolModule", c_void_p), + ("GetTypeName", c_void_p), + ("GetTypeId", c_void_p), + ("GetTypeSize", c_void_p), + ("GetFieldOffset", c_void_p), + ("GetSymbolTypeId", c_void_p), + ("GetOffsetTypeId", c_void_p), + ("ReadTypedDataVirtual", c_void_p), + ("WriteTypedDataVirtual", c_void_p), + ("OutputTypedDataVirtual", c_void_p), + ("ReadTypedDataPhysical", c_void_p), + ("WriteTypedDataPhysical", c_void_p), + ("OutputTypedDataPhysical", c_void_p), + ("GetScope", c_void_p), + ("SetScope", c_void_p), + ("ResetScope", c_void_p), + ("GetScopeSymbolGroup", c_void_p), + ("CreateSymbolGroup", c_void_p), + ("StartSymbolMatch", c_void_p), + ("GetNextSymbolMatch", c_void_p), + ("EndSymbolMatch", c_void_p), + ("Reload", c_void_p), + ("GetSymbolPath", c_void_p), + ("SetSymbolPath", c_void_p), + ("AppendSymbolPath", c_void_p), + ("GetImagePath", c_void_p), + ("SetImagePath", c_void_p), + ("AppendImagePath", c_void_p), + ("GetSourcePath", c_void_p), + ("GetSourcePathElement", c_void_p), + ("SetSourcePath", c_void_p), + ("AppendSourcePath", c_void_p), + ("FindSourceFile", c_void_p), + ("GetSourceFileLineOffsets", c_void_p), + ("GetModuleVersionInformation", c_void_p), + ("GetModuleNameString", ids_getmodulenamestring), + ("GetConstantName", c_void_p), + ("GetFieldName", c_void_p), + ("GetTypeOptions", c_void_p), + ("AddTypeOptions", c_void_p), + ("RemoveTypeOptions", c_void_p), + ("SetTypeOptions", c_void_p), + ("GetNameByOffsetWide", c_void_p), + ("GetOffsetByNameWide", c_void_p), + ("GetNearNameByOffsetWide", c_void_p), + ("GetLineByOffsetWide", c_void_p), + ("GetOffsetByLineWide", c_void_p), + ("GetModuleByModuleNameWide", c_void_p), + ("GetSymbolModuleWide", c_void_p), + ("GetTypeNameWide", c_void_p), + ("GetTypeIdWide", c_void_p), + ("GetFieldOffsetWide", c_void_p), + ("GetSymbolTypeIdWide", c_void_p), + ("GetScopeSymbolGroup2", ids_getscopesymbolgroup2), + ("CreateSymbolGroup2", c_void_p), + ("StartSymbolMatchWide", c_void_p), + ("GetNextSymbolMatchWide", c_void_p), + ("ReloadWide", c_void_p), + ("GetSymbolPathWide", c_void_p), + ("SetSymbolPathWide", c_void_p), + ("AppendSymbolPathWide", c_void_p), + ("GetImagePathWide", c_void_p), + ("SetImagePathWide", c_void_p), + ("AppendImagePathWide", c_void_p), + ("GetSourcePathWide", c_void_p), + ("GetSourcePathElementWide", c_void_p), + ("SetSourcePathWide", c_void_p), + ("AppendSourcePathWide", c_void_p), + ("FindSourceFileWide", c_void_p), + ("GetSourceFileLineOffsetsWide", c_void_p), + ("GetModuleVersionInformationWide", c_void_p), + ("GetModuleNameStringWide", c_void_p), + ("GetConstantNameWide", c_void_p), + ("GetFieldNameWide", c_void_p), + ("IsManagedModule", c_void_p), + ("GetModuleByModuleName2", c_void_p), + ("GetModuleByModuleName2Wide", c_void_p), + ("GetModuleByOffset2", c_void_p), + ("AddSyntheticModule", c_void_p), + ("AddSyntheticModuleWide", c_void_p), + ("RemoveSyntheticModule", c_void_p), + ("GetCurrentScopeFrameIndex", ids_getcurrentscopeframeindex), + ("SetScopeFrameByIndex", ids_setscopeframebyindex), + ("SetScopeFromJitDebugInfo", c_void_p), + ("SetScopeFromStoredEvent", c_void_p), + ("OutputSymbolByOffset", c_void_p), + ("GetFunctionEntryByOffset", c_void_p), + ("GetFieldTypeAndOffset", c_void_p), + ("GetFieldTypeAndOffsetWide", c_void_p), + ("AddSyntheticSymbol", c_void_p), + ("AddSyntheticSymbolWide", c_void_p), + ("RemoveSyntheticSymbol", c_void_p), + ("GetSymbolEntriesByOffset", c_void_p), + ("GetSymbolEntriesByName", ids_getsymbolentriesbyname), + ("GetSymbolEntriesByNameWide", c_void_p), + ("GetSymbolEntryByToken", c_void_p), + ("GetSymbolEntryInformation", ids_getsymbolentryinformation), + ("GetSymbolEntryString", ids_getsymbolentrystring), + ("GetSymbolEntryStringWide", c_void_p), + ("GetSymbolEntryOffsetRegions", c_void_p), + ("GetSymbolEntryBySymbolEntry", c_void_p), + ("GetSourceEntriesByOffset", c_void_p), + ("GetSourceEntriesByLine", c_void_p), + ("GetSourceEntriesByLineWide", c_void_p), + ("GetSourceEntryString", c_void_p), + ("GetSourceEntryStringWide", c_void_p), + ("GetSourceEntryOffsetRegions", c_void_p), + ("GetsourceEntryBySourceEntry", c_void_p), + ("GetScopeEx", c_void_p), + ("SetScopeEx", c_void_p), + ("GetNameByInlineContext", ids_getnamebyinlinecontext), + ("GetNameByInlineContextWide", c_void_p), + ("GetLineByInlineContext", ids_getlinebyinlinecontext), + ("GetLineByInlineContextWide", c_void_p), + ("OutputSymbolByInlineContext", c_void_p), + ("GetCurrentScopeFrameIndexEx", c_void_p), + ("SetScopeFrameByIndexEx", c_void_p) + ] + +IDebugSymbols5._fields_ = [("lpVtbl", POINTER(IDebugSymbols5Vtbl))] + +SymbolId = namedtuple("SymbolId", ["ModuleBase", "Id"]) +SymbolEntry = namedtuple("SymbolEntry", ["ModuleBase", "Offset", "Id", "Arg64", "Size", "Flags", "TypeId", "NameSize", "Token", "Tag", "Arg32"]) +DebugModuleParams = namedtuple("DebugModuleParams", ["Base", "Size", "TimeDateStamp", "Checksum", "Flags", "SymbolType", "ImageNameSize", "ModuleNameSize", "LoadedImageNameSize", "SymbolFileNameSize", "MappedImageNameSize"]) + +class SymTags(IntEnum): + Null = 0 + Exe = 1 + SymTagFunction = 5 + +def make_debug_module_params(cdata): + fieldvalues = map(lambda y: getattr(cdata, y), DebugModuleParams._fields) + return DebugModuleParams(*fieldvalues) + +class Symbols(object): + def __init__(self, symbols): + self.ptr = symbols + self.symbols = symbols.contents + self.vt = self.symbols.lpVtbl.contents + # Keep some handy ulongs for passing into C methods. + self.ulong = c_ulong() + self.ulong64 = c_ulonglong() + + def GetCurrentScopeFrameIndex(self): + res = self.vt.GetCurrentScopeFrameIndex(self.symbols, byref(self.ulong)) + aborter(res, "GetCurrentScopeFrameIndex") + return self.ulong.value + + def SetScopeFrameByIndex(self, idx): + res = self.vt.SetScopeFrameByIndex(self.symbols, idx) + aborter(res, "SetScopeFrameByIndex", ignore=[E_EINVAL]) + return res != E_EINVAL + + def GetOffsetByName(self, name): + res = self.vt.GetOffsetByName(self.symbols, name.encode("ascii"), byref(self.ulong64)) + aborter(res, "GetOffsetByName {}".format(name)) + return self.ulong64.value + + def GetNearNameByOffset(self, addr): + ptr = create_string_buffer(256) + pulong = c_ulong() + disp = c_ulonglong() + # Zero arg -> "delta" indicating how many symbols to skip + res = self.vt.GetNearNameByOffset(self.symbols, addr, 0, ptr, 255, byref(pulong), byref(disp)) + if res == E_NOINTERFACE: + return "{noname}" + aborter(res, "GetNearNameByOffset") + ptr[255] = '\0'.encode("ascii") + return '{}+{}'.format(string_at(ptr).decode("ascii"), disp.value) + + def GetModuleByModuleName2(self, name): + # First zero arg -> module index to search from, second zero arg -> + # DEBUG_GETMOD_* flags, none of which we use. + res = self.vt.GetModuleByModuleName2(self.symbols, name, 0, 0, None, byref(self.ulong64)) + aborter(res, "GetModuleByModuleName2") + return self.ulong64.value + + def GetScopeSymbolGroup2(self): + retptr = POINTER(IDebugSymbolGroup2)() + res = self.vt.GetScopeSymbolGroup2(self.symbols, ScopeGroupFlags.DEBUG_SCOPE_GROUP_ALL, None, retptr) + aborter(res, "GetScopeSymbolGroup2") + return SymbolGroup(retptr) + + def GetSymbolEntryString(self, idx, module): + symid = DEBUG_MODULE_AND_ID() + symid.ModuleBase = module + symid.Id = idx + ptr = create_string_buffer(1024) + # Zero arg is the string index -- symbols can have multiple names, for now + # only support the first one. + res = self.vt.GetSymbolEntryString(self.symbols, symid, 0, ptr, 1023, byref(self.ulong)) + aborter(res, "GetSymbolEntryString") + return string_at(ptr).decode("ascii") + + def GetSymbolEntryInformation(self, module, theid): + symid = DEBUG_MODULE_AND_ID() + symentry = DEBUG_SYMBOL_ENTRY() + symid.ModuleBase = module + symid.Id = theid + res = self.vt.GetSymbolEntryInformation(self.symbols, symid, symentry) + aborter(res, "GetSymbolEntryInformation") + # Fetch fields into SymbolEntry object + fields = map(lambda x: getattr(symentry, x), SymbolEntry._fields) + return SymbolEntry(*fields) + + def GetSymbolEntriesByName(self, symstr): + # Initial query to find number of symbol entries + res = self.vt.GetSymbolEntriesByName(self.symbols, symstr.encode("ascii"), 0, None, 0, byref(self.ulong)) + aborter(res, "GetSymbolEntriesByName") + + # Build a buffer and query for 'length' entries + length = self.ulong.value + symrecs = (DEBUG_MODULE_AND_ID * length)() + # Zero arg -> flags, of which there are none defined. + res = self.vt.GetSymbolEntriesByName(self.symbols, symstr.encode("ascii"), 0, symrecs, length, byref(self.ulong)) + aborter(res, "GetSymbolEntriesByName") + + # Extract 'length' number of SymbolIds + length = self.ulong.value + def extract(x): + sym = symrecs[x] + return SymbolId(sym.ModuleBase, sym.Id) + return [extract(x) for x in range(length)] + + def GetSymbolPath(self): + # Query for length of buffer to allocate + res = self.vt.GetSymbolPath(self.symbols, None, 0, byref(self.ulong)) + aborter(res, "GetSymbolPath", ignore=[S_FALSE]) + + # Fetch 'length' length symbol path string + length = self.ulong.value + arr = create_string_buffer(length) + res = self.vt.GetSymbolPath(self.symbols, arr, length, byref(self.ulong)) + aborter(res, "GetSymbolPath") + + return string_at(arr).decode("ascii") + + def GetSourcePath(self): + # Query for length of buffer to allocate + res = self.vt.GetSourcePath(self.symbols, None, 0, byref(self.ulong)) + aborter(res, "GetSourcePath", ignore=[S_FALSE]) + + # Fetch a string of len 'length' + length = self.ulong.value + arr = create_string_buffer(length) + res = self.vt.GetSourcePath(self.symbols, arr, length, byref(self.ulong)) + aborter(res, "GetSourcePath") + + return string_at(arr).decode("ascii") + + def SetSourcePath(self, string): + res = self.vt.SetSourcePath(self.symbols, string.encode("ascii")) + aborter(res, "SetSourcePath") + return + + def GetModuleParameters(self, base): + self.ulong64.value = base + params = DEBUG_MODULE_PARAMETERS() + # Fetch one module params struct, starting at idx zero + res = self.vt.GetModuleParameters(self.symbols, 1, byref(self.ulong64), 0, byref(params)) + aborter(res, "GetModuleParameters") + return make_debug_module_params(params) + + def GetSymbolOptions(self): + res = self.vt.GetSymbolOptions(self.symbols, byref(self.ulong)) + aborter(res, "GetSymbolOptions") + return SymbolOptionFlags(self.ulong.value) + + def SetSymbolOptions(self, opts): + assert isinstance(opts, SymbolOptionFlags) + res = self.vt.SetSymbolOptions(self.symbols, opts.value) + aborter(res, "SetSymbolOptions") + return + + def GetLineByOffset(self, offs): + # Initial query for filename buffer size + res = self.vt.GetLineByOffset(self.symbols, offs, None, None, 0, byref(self.ulong), None) + if res == E_FAIL: + return None # Sometimes we just can't get line numbers, of course + aborter(res, "GetLineByOffset", ignore=[S_FALSE]) + + # Allocate filename buffer and query for line number too + filenamelen = self.ulong.value + text = create_string_buffer(filenamelen) + line = c_ulong() + res = self.vt.GetLineByOffset(self.symbols, offs, byref(line), text, filenamelen, byref(self.ulong), None) + aborter(res, "GetLineByOffset") + + return string_at(text).decode("ascii"), line.value + + def GetModuleNameString(self, whichname, base): + # Initial query for name string length + res = self.vt.GetModuleNameString(self.symbols, whichname, DEBUG_ANY_ID, base, None, 0, byref(self.ulong)) + aborter(res, "GetModuleNameString", ignore=[S_FALSE]) + + module_name_len = self.ulong.value + module_name = (c_char * module_name_len)() + res = self.vt.GetModuleNameString(self.symbols, whichname, DEBUG_ANY_ID, base, module_name, module_name_len, None) + aborter(res, "GetModuleNameString") + + return string_at(module_name).decode("ascii") + + def GetNameByInlineContext(self, pc, ctx): + # None args -> ignore output name size and displacement + buf = create_string_buffer(256) + res = self.vt.GetNameByInlineContext(self.symbols, pc, ctx, buf, 255, None, None) + aborter(res, "GetNameByInlineContext") + return string_at(buf).decode("ascii") + + def GetLineByInlineContext(self, pc, ctx): + # None args -> ignore output filename size and displacement + buf = create_string_buffer(256) + res = self.vt.GetLineByInlineContext(self.symbols, pc, ctx, byref(self.ulong), buf, 255, None, None) + aborter(res, "GetLineByInlineContext") + return string_at(buf).decode("ascii"), self.ulong.value + + def get_all_symbols(self): + main_module_name = self.get_exefile_module_name() + idnumbers = self.GetSymbolEntriesByName("{}!*".format(main_module_name)) + lst = [] + for symid in idnumbers: + s = self.GetSymbolEntryString(symid.Id, symid.ModuleBase) + symentry = self.GetSymbolEntryInformation(symid.ModuleBase, symid.Id) + lst.append((s, symentry)) + return lst + + def get_all_functions(self): + syms = self.get_all_symbols() + return [x for x in syms if x[1].Tag == SymTags.SymTagFunction] + + def get_all_modules(self): + params = DEBUG_MODULE_PARAMETERS() + idx = 0 + res = 0 + all_modules = [] + while res != E_EINVAL: + res = self.vt.GetModuleParameters(self.symbols, 1, None, idx, byref(params)) + aborter(res, "GetModuleParameters", ignore=[E_EINVAL]) + all_modules.append(make_debug_module_params(params)) + idx += 1 + return all_modules + + def get_exefile_module(self): + all_modules = self.get_all_modules() + reduce_func = lambda x, y: y if y.Flags & DebugModuleFlags.DEBUG_MODULE_EXE_MODULE else x + main_module = reduce(reduce_func, all_modules, None) + if main_module is None: + raise Exception("Couldn't find the exefile module") + return main_module + + def get_module_name(self, base): + return self.GetModuleNameString(DebugModuleNames.DEBUG_MODNAME_MODULE, base) + + def get_exefile_module_name(self): + return self.get_module_name(self.get_exefile_module().Base) diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/symgroup.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/symgroup.py new file mode 100644 index 00000000000..2775af3279b --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/symgroup.py @@ -0,0 +1,98 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from collections import namedtuple +from ctypes import * +from functools import partial + +from .utils import * + +Symbol = namedtuple("Symbol", ["num", "name", "type", "value"]) + +class IDebugSymbolGroup2(Structure): + pass + +class IDebugSymbolGroup2Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSymbolGroup2)) + ids_getnumbersymbols = wrp(c_ulong_p) + ids_getsymbolname = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p) + ids_getsymboltypename = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p) + ids_getsymbolvaluetext = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p) + _fields_ = [ + ("QueryInterface", c_void_p), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("GetNumberSymbols", ids_getnumbersymbols), + ("AddSymbol", c_void_p), + ("RemoveSymbolByName", c_void_p), + ("RemoveSymbolByIndex", c_void_p), + ("GetSymbolName", ids_getsymbolname), + ("GetSymbolParameters", c_void_p), + ("ExpandSymbol", c_void_p), + ("OutputSymbols", c_void_p), + ("WriteSymbol", c_void_p), + ("OutputAsType", c_void_p), + ("AddSymbolWide", c_void_p), + ("RemoveSymbolByNameWide", c_void_p), + ("GetSymbolNameWide", c_void_p), + ("WritesymbolWide", c_void_p), + ("OutputAsTypeWide", c_void_p), + ("GetSymbolTypeName", ids_getsymboltypename), + ("GetSymbolTypeNameWide", c_void_p), + ("GetSymbolSize", c_void_p), + ("GetSymbolOffset", c_void_p), + ("GetSymbolRegister", c_void_p), + ("GetSymbolValueText", ids_getsymbolvaluetext), + ("GetSymbolValueTextWide", c_void_p), + ("GetSymbolEntryInformation", c_void_p) + ] + +IDebugSymbolGroup2._fields_ = [("lpVtbl", POINTER(IDebugSymbolGroup2Vtbl))] + +class SymbolGroup(object): + def __init__(self, symgroup): + self.symgroup = symgroup.contents + self.vt = self.symgroup.lpVtbl.contents + self.ulong = c_ulong() + + def GetNumberSymbols(self): + res = self.vt.GetNumberSymbols(self.symgroup, byref(self.ulong)) + aborter(res, "GetNumberSymbols") + return self.ulong.value + + def GetSymbolName(self, idx): + buf = create_string_buffer(256) + res = self.vt.GetSymbolName(self.symgroup, idx, buf, 255, byref(self.ulong)) + aborter(res, "GetSymbolName") + thelen = self.ulong.value + return string_at(buf).decode("ascii") + + def GetSymbolTypeName(self, idx): + buf = create_string_buffer(256) + res = self.vt.GetSymbolTypeName(self.symgroup, idx, buf, 255, byref(self.ulong)) + aborter(res, "GetSymbolTypeName") + thelen = self.ulong.value + return string_at(buf).decode("ascii") + + def GetSymbolValueText(self, idx, handleserror=False): + buf = create_string_buffer(256) + res = self.vt.GetSymbolValueText(self.symgroup, idx, buf, 255, byref(self.ulong)) + if res != 0 and handleserror: + return None + aborter(res, "GetSymbolTypeName") + thelen = self.ulong.value + return string_at(buf).decode("ascii") + + def get_symbol(self, idx): + name = self.GetSymbolName(idx) + thetype = self.GetSymbolTypeName(idx) + value = self.GetSymbolValueText(idx) + return Symbol(idx, name, thetype, value) + + def get_all_symbols(self): + num_syms = self.GetNumberSymbols() + return list(map(self.get_symbol, list(range(num_syms)))) diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/sysobjs.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/sysobjs.py new file mode 100644 index 00000000000..0e9844a363b --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/sysobjs.py @@ -0,0 +1,200 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * +from functools import partial + +from .utils import * + +# UUID For SystemObjects4 interface. +DebugSystemObjects4IID = IID(0x489468e6, 0x7d0f, 0x4af5, IID_Data4_Type(0x87, 0xab, 0x25, 0x20, 0x74, 0x54, 0xd5, 0x53)) + +class IDebugSystemObjects4(Structure): + pass + +class IDebugSystemObjects4Vtbl(Structure): + wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSystemObjects4)) + ids_getnumberprocesses = wrp(POINTER(c_ulong)) + ids_getprocessidsbyindex = wrp(c_ulong, c_ulong, c_ulong_p, c_ulong_p) + ids_setcurrentprocessid = wrp(c_ulong) + ids_getnumberthreads = wrp(c_ulong_p) + ids_getthreadidsbyindex = wrp(c_ulong, c_ulong, c_ulong_p, c_ulong_p) + ids_setcurrentthreadid = wrp(c_ulong) + _fields_ = [ + ("QueryInterface", c_void_p), + ("AddRef", c_void_p), + ("Release", c_void_p), + ("GetEventThread", c_void_p), + ("GetEventProcess", c_void_p), + ("GetCurrentThreadId", c_void_p), + ("SetCurrentThreadId", ids_setcurrentthreadid), + ("GetCurrentProcessId", c_void_p), + ("SetCurrentProcessId", ids_setcurrentprocessid), + ("GetNumberThreads", ids_getnumberthreads), + ("GetTotalNumberThreads", c_void_p), + ("GetThreadIdsByIndex", ids_getthreadidsbyindex), + ("GetThreadIdByProcessor", c_void_p), + ("GetCurrentThreadDataOffset", c_void_p), + ("GetThreadIdByDataOffset", c_void_p), + ("GetCurrentThreadTeb", c_void_p), + ("GetThreadIdByTeb", c_void_p), + ("GetCurrentThreadSystemId", c_void_p), + ("GetThreadIdBySystemId", c_void_p), + ("GetCurrentThreadHandle", c_void_p), + ("GetThreadIdByHandle", c_void_p), + ("GetNumberProcesses", ids_getnumberprocesses), + ("GetProcessIdsByIndex", ids_getprocessidsbyindex), + ("GetCurrentProcessDataOffset", c_void_p), + ("GetProcessIdByDataOffset", c_void_p), + ("GetCurrentProcessPeb", c_void_p), + ("GetProcessIdByPeb", c_void_p), + ("GetCurrentProcessSystemId", c_void_p), + ("GetProcessIdBySystemId", c_void_p), + ("GetCurrentProcessHandle", c_void_p), + ("GetProcessIdByHandle", c_void_p), + ("GetCurrentProcessExecutableName", c_void_p), + ("GetCurrentProcessUpTime", c_void_p), + ("GetImplicitThreadDataOffset", c_void_p), + ("SetImplicitThreadDataOffset", c_void_p), + ("GetImplicitProcessDataOffset", c_void_p), + ("SetImplicitProcessDataOffset", c_void_p), + ("GetEventSystem", c_void_p), + ("GetCurrentSystemId", c_void_p), + ("SetCurrentSystemId", c_void_p), + ("GetNumberSystems", c_void_p), + ("GetSystemIdsByIndex", c_void_p), + ("GetTotalNumberThreadsAndProcesses", c_void_p), + ("GetCurrentSystemServer", c_void_p), + ("GetSystemByServer", c_void_p), + ("GetCurrentSystemServerName", c_void_p), + ("GetCurrentProcessExecutableNameWide", c_void_p), + ("GetCurrentSystemServerNameWide", c_void_p) + ] + +IDebugSystemObjects4._fields_ = [("lpVtbl", POINTER(IDebugSystemObjects4Vtbl))] + +class SysObjects(object): + def __init__(self, sysobjects): + self.ptr = sysobjects + self.sysobjects = sysobjects.contents + self.vt = self.sysobjects.lpVtbl.contents + # Keep a handy ulong for passing into C methods. + self.ulong = c_ulong() + + def GetNumberSystems(self): + res = self.vt.GetNumberSystems(self.sysobjects, byref(self.ulong)) + aborter(res, "GetNumberSystems") + return self.ulong.value + + def GetNumberProcesses(self): + res = self.vt.GetNumberProcesses(self.sysobjects, byref(self.ulong)) + aborter(res, "GetNumberProcesses") + return self.ulong.value + + def GetNumberThreads(self): + res = self.vt.GetNumberThreads(self.sysobjects, byref(self.ulong)) + aborter(res, "GetNumberThreads") + return self.ulong.value + + def GetTotalNumberThreadsAndProcesses(self): + tthreads = c_ulong() + tprocs = c_ulong() + pulong3 = c_ulong() + res = self.vt.GetTotalNumberThreadsAndProcesses(self.sysobjects, byref(tthreads), byref(tprocs), byref(pulong3), byref(pulong3), byref(pulong3)) + aborter(res, "GettotalNumberThreadsAndProcesses") + return tthreads.value, tprocs.value + + def GetCurrentProcessId(self): + res = self.vt.GetCurrentProcessId(self.sysobjects, byref(self.ulong)) + aborter(res, "GetCurrentProcessId") + return self.ulong.value + + def SetCurrentProcessId(self, sysid): + res = self.vt.SetCurrentProcessId(self.sysobjects, sysid) + aborter(res, "SetCurrentProcessId") + return + + def GetCurrentThreadId(self): + res = self.vt.GetCurrentThreadId(self.sysobjects, byref(self.ulong)) + aborter(res, "GetCurrentThreadId") + return self.ulong.value + + def SetCurrentThreadId(self, sysid): + res = self.vt.SetCurrentThreadId(self.sysobjects, sysid) + aborter(res, "SetCurrentThreadId") + return + + def GetProcessIdsByIndex(self): + num_processes = self.GetNumberProcesses() + if num_processes == 0: + return [] + engineids = (c_ulong * num_processes)() + pids = (c_ulong * num_processes)() + for x in range(num_processes): + engineids[x] = DEBUG_ANY_ID + pids[x] = DEBUG_ANY_ID + res = self.vt.GetProcessIdsByIndex(self.sysobjects, 0, num_processes, engineids, pids) + aborter(res, "GetProcessIdsByIndex") + return list(zip(engineids, pids)) + + def GetThreadIdsByIndex(self): + num_threads = self.GetNumberThreads() + if num_threads == 0: + return [] + engineids = (c_ulong * num_threads)() + tids = (c_ulong * num_threads)() + for x in range(num_threads): + engineids[x] = DEBUG_ANY_ID + tids[x] = DEBUG_ANY_ID + # Zero -> start index + res = self.vt.GetThreadIdsByIndex(self.sysobjects, 0, num_threads, engineids, tids) + aborter(res, "GetThreadIdsByIndex") + return list(zip(engineids, tids)) + + def GetCurThreadHandle(self): + pulong64 = c_ulonglong() + res = self.vt.GetCurrentThreadHandle(self.sysobjects, byref(pulong64)) + aborter(res, "GetCurrentThreadHandle") + return pulong64.value + + def set_current_thread(self, pid, tid): + proc_sys_id = -1 + for x in self.GetProcessIdsByIndex(): + sysid, procid = x + if procid == pid: + proc_sys_id = sysid + + if proc_sys_id == -1: + raise Exception("Couldn't find designated PID {}".format(pid)) + + self.SetCurrentProcessId(proc_sys_id) + + thread_sys_id = -1 + for x in self.GetThreadIdsByIndex(): + sysid, threadid = x + if threadid == tid: + thread_sys_id = sysid + + if thread_sys_id == -1: + raise Exception("Couldn't find designated TID {}".format(tid)) + + self.SetCurrentThreadId(thread_sys_id) + return + + def print_current_procs_threads(self): + procs = [] + for x in self.GetProcessIdsByIndex(): + sysid, procid = x + procs.append(procid) + + threads = [] + for x in self.GetThreadIdsByIndex(): + sysid, threadid = x + threads.append(threadid) + + print("Current processes: {}".format(procs)) + print("Current threads: {}".format(threads)) diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/utils.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/utils.py new file mode 100644 index 00000000000..0c9197aa1c9 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/utils.py @@ -0,0 +1,47 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ctypes import * + +# Error codes are negative when received by python, but are typically +# represented by unsigned hex elsewhere. Subtract 2^32 from the unsigned +# hex to produce negative error codes. +E_NOINTERFACE = 0x80004002 - 0x100000000 +E_FAIL = 0x80004005 - 0x100000000 +E_EINVAL = 0x80070057 - 0x100000000 +E_INTERNALEXCEPTION = 0x80040205 - 0x100000000 +S_FALSE = 1 + +# This doesn't fit into any convenient category +DEBUG_ANY_ID = 0xFFFFFFFF + +class WinError(Exception): + def __init__(self, msg, hstatus): + self.hstatus = hstatus + super(WinError, self).__init__(msg) + +def aborter(res, msg, ignore=[]): + if res != 0 and res not in ignore: + # Convert a negative error code to a positive unsigned one, which is + # now NTSTATUSes appear in documentation. + if res < 0: + res += 0x100000000 + msg = '{:08X} : {}'.format(res, msg) + raise WinError(msg, res) + +IID_Data4_Type = c_ubyte * 8 + +class IID(Structure): + _fields_ = [ + ("Data1", c_uint), + ("Data2", c_ushort), + ("Data3", c_ushort), + ("Data4", IID_Data4_Type) + ] + +c_ulong_p = POINTER(c_ulong) +c_ulong64_p = POINTER(c_ulonglong) diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py new file mode 100644 index 00000000000..425d3c2adb1 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py @@ -0,0 +1,244 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Interface for communicating with the LLDB debugger via its python interface. +""" + +import imp +import os +from subprocess import CalledProcessError, check_output, STDOUT +import sys + +from dex.debugger.DebuggerBase import DebuggerBase +from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR +from dex.dextIR import StackFrame, SourceLocation, ProgramState +from dex.utils.Exceptions import DebuggerException, LoadDebuggerException +from dex.utils.ReturnCode import ReturnCode + + +class LLDB(DebuggerBase): + def __init__(self, context, *args): + self.lldb_executable = context.options.lldb_executable + self._debugger = None + self._target = None + self._process = None + self._thread = None + super(LLDB, self).__init__(context, *args) + + def _custom_init(self): + self._debugger = self._interface.SBDebugger.Create() + self._debugger.SetAsync(False) + self._target = self._debugger.CreateTargetWithFileAndArch( + self.context.options.executable, self.context.options.arch) + if not self._target: + raise LoadDebuggerException( + 'could not create target for executable "{}" with arch:{}'. + format(self.context.options.executable, + self.context.options.arch)) + + def _custom_exit(self): + if getattr(self, '_process', None): + self._process.Kill() + if getattr(self, '_debugger', None) and getattr(self, '_target', None): + self._debugger.DeleteTarget(self._target) + + def _translate_stop_reason(self, reason): + if reason == self._interface.eStopReasonNone: + return None + if reason == self._interface.eStopReasonBreakpoint: + return StopReason.BREAKPOINT + if reason == self._interface.eStopReasonPlanComplete: + return StopReason.STEP + if reason == self._interface.eStopReasonThreadExiting: + return StopReason.PROGRAM_EXIT + if reason == self._interface.eStopReasonException: + return StopReason.ERROR + return StopReason.OTHER + + def _load_interface(self): + try: + args = [self.lldb_executable, '-P'] + pythonpath = check_output( + args, stderr=STDOUT).rstrip().decode('utf-8') + except CalledProcessError as e: + raise LoadDebuggerException(str(e), sys.exc_info()) + except OSError as e: + raise LoadDebuggerException( + '{} ["{}"]'.format(e.strerror, self.lldb_executable), + sys.exc_info()) + + if not os.path.isdir(pythonpath): + raise LoadDebuggerException( + 'path "{}" does not exist [result of {}]'.format( + pythonpath, args), sys.exc_info()) + + try: + module_info = imp.find_module('lldb', [pythonpath]) + return imp.load_module('lldb', *module_info) + except ImportError as e: + msg = str(e) + if msg.endswith('not a valid Win32 application.'): + msg = '{} [Are you mixing 32-bit and 64-bit binaries?]'.format( + msg) + raise LoadDebuggerException(msg, sys.exc_info()) + + @classmethod + def get_name(cls): + return 'lldb' + + @classmethod + def get_option_name(cls): + return 'lldb' + + @property + def version(self): + try: + return self._interface.SBDebugger_GetVersionString() + except AttributeError: + return None + + def clear_breakpoints(self): + self._target.DeleteAllBreakpoints() + + def add_breakpoint(self, file_, line): + if not self._target.BreakpointCreateByLocation(file_, line): + raise LoadDebuggerException( + 'could not add breakpoint [{}:{}]'.format(file_, line)) + + def launch(self): + self._process = self._target.LaunchSimple(None, None, os.getcwd()) + if not self._process or self._process.GetNumThreads() == 0: + raise DebuggerException('could not launch process') + if self._process.GetNumThreads() != 1: + raise DebuggerException('multiple threads not supported') + self._thread = self._process.GetThreadAtIndex(0) + assert self._thread, (self._process, self._thread) + + def step(self): + self._thread.StepInto() + + def go(self) -> ReturnCode: + self._process.Continue() + return ReturnCode.OK + + def get_step_info(self): + frames = [] + state_frames = [] + + for i in range(0, self._thread.GetNumFrames()): + sb_frame = self._thread.GetFrameAtIndex(i) + sb_line = sb_frame.GetLineEntry() + sb_filespec = sb_line.GetFileSpec() + + try: + path = os.path.join(sb_filespec.GetDirectory(), + sb_filespec.GetFilename()) + except (AttributeError, TypeError): + path = None + + function = self._sanitize_function_name(sb_frame.GetFunctionName()) + + loc_dict = { + 'path': path, + 'lineno': sb_line.GetLine(), + 'column': sb_line.GetColumn() + } + loc = LocIR(**loc_dict) + + frame = FrameIR( + function=function, is_inlined=sb_frame.IsInlined(), loc=loc) + + if any( + name in (frame.function or '') # pylint: disable=no-member + for name in self.frames_below_main): + break + + frames.append(frame) + + state_frame = StackFrame(function=frame.function, + is_inlined=frame.is_inlined, + location=SourceLocation(**loc_dict), + watches={}) + for expr in map( + lambda watch, idx=i: self.evaluate_expression(watch, idx), + self.watches): + state_frame.watches[expr.expression] = expr + state_frames.append(state_frame) + + if len(frames) == 1 and frames[0].function is None: + frames = [] + state_frames = [] + + reason = self._translate_stop_reason(self._thread.GetStopReason()) + + return StepIR( + step_index=self.step_index, frames=frames, stop_reason=reason, + program_state=ProgramState(state_frames)) + + @property + def is_running(self): + # We're not running in async mode so this is always False. + return False + + @property + def is_finished(self): + return not self._thread.GetFrameAtIndex(0) + + @property + def frames_below_main(self): + return ['__scrt_common_main_seh', '__libc_start_main'] + + def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: + result = self._thread.GetFrameAtIndex(frame_idx + ).EvaluateExpression(expression) + error_string = str(result.error) + + value = result.value + could_evaluate = not any(s in error_string for s in [ + "Can't run the expression locally", + "use of undeclared identifier", + "no member named", + "Couldn't lookup symbols", + "reference to local variable", + "invalid use of 'this' outside of a non-static member function", + ]) + + is_optimized_away = any(s in error_string for s in [ + 'value may have been optimized out', + ]) + + is_irretrievable = any(s in error_string for s in [ + "couldn't get the value of variable", + "couldn't read its memory", + "couldn't read from memory", + "Cannot access memory at address", + "invalid address (fault address:", + ]) + + if could_evaluate and not is_irretrievable and not is_optimized_away: + assert error_string == 'success', (error_string, expression, value) + # assert result.value is not None, (result.value, expression) + + if error_string == 'success': + error_string = None + + # attempt to find expression as a variable, if found, take the variable + # obj's type information as it's 'usually' more accurate. + var_result = self._thread.GetFrameAtIndex(frame_idx).FindVariable(expression) + if str(var_result.error) == 'success': + type_name = var_result.type.GetDisplayTypeName() + else: + type_name = result.type.GetDisplayTypeName() + + return ValueIR( + expression=expression, + value=value, + type_name=type_name, + error_string=error_string, + could_evaluate=could_evaluate, + is_optimized_away=is_optimized_away, + is_irretrievable=is_irretrievable, + ) diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/__init__.py b/debuginfo-tests/dexter/dex/debugger/lldb/__init__.py new file mode 100644 index 00000000000..1282f2ddc90 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/lldb/__init__.py @@ -0,0 +1,8 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from dex.debugger.lldb.LLDB import LLDB diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py new file mode 100644 index 00000000000..596dc31ab4a --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py @@ -0,0 +1,224 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Interface for communicating with the Visual Studio debugger via DTE.""" + +import abc +import imp +import os +import sys + +from dex.debugger.DebuggerBase import DebuggerBase +from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR +from dex.dextIR import StackFrame, SourceLocation, ProgramState +from dex.utils.Exceptions import Error, LoadDebuggerException +from dex.utils.ReturnCode import ReturnCode + + +def _load_com_module(): + try: + module_info = imp.find_module( + 'ComInterface', + [os.path.join(os.path.dirname(__file__), 'windows')]) + return imp.load_module('ComInterface', *module_info) + except ImportError as e: + raise LoadDebuggerException(e, sys.exc_info()) + + +class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abstract-method + + # Constants for results of Debugger.CurrentMode + # (https://msdn.microsoft.com/en-us/library/envdte.debugger.currentmode.aspx) + dbgDesignMode = 1 + dbgBreakMode = 2 + dbgRunMode = 3 + + def __init__(self, *args): + self.com_module = None + self._debugger = None + self._solution = None + self._fn_step = None + self._fn_go = None + super(VisualStudio, self).__init__(*args) + + def _custom_init(self): + try: + self._debugger = self._interface.Debugger + self._debugger.HexDisplayMode = False + + self._interface.MainWindow.Visible = ( + self.context.options.show_debugger) + + self._solution = self._interface.Solution + self._solution.Create(self.context.working_directory.path, + 'DexterSolution') + + try: + self._solution.AddFromFile(self._project_file) + except OSError: + raise LoadDebuggerException( + 'could not debug the specified executable', sys.exc_info()) + + self._fn_step = self._debugger.StepInto + self._fn_go = self._debugger.Go + + except AttributeError as e: + raise LoadDebuggerException(str(e), sys.exc_info()) + + def _custom_exit(self): + if self._interface: + self._interface.Quit() + + @property + def _project_file(self): + return self.context.options.executable + + @abc.abstractproperty + def _dte_version(self): + pass + + @property + def _location(self): + bp = self._debugger.BreakpointLastHit + return { + 'path': getattr(bp, 'File', None), + 'lineno': getattr(bp, 'FileLine', None), + 'column': getattr(bp, 'FileColumn', None) + } + + @property + def _mode(self): + return self._debugger.CurrentMode + + def _load_interface(self): + self.com_module = _load_com_module() + return self.com_module.DTE(self._dte_version) + + @property + def version(self): + try: + return self._interface.Version + except AttributeError: + return None + + def clear_breakpoints(self): + for bp in self._debugger.Breakpoints: + bp.Delete() + + def add_breakpoint(self, file_, line): + self._debugger.Breakpoints.Add('', file_, line) + + def launch(self): + self.step() + + def step(self): + self._fn_step() + + def go(self) -> ReturnCode: + self._fn_go() + return ReturnCode.OK + + def set_current_stack_frame(self, idx: int = 0): + thread = self._debugger.CurrentThread + stack_frames = thread.StackFrames + try: + stack_frame = stack_frames[idx] + self._debugger.CurrentStackFrame = stack_frame.raw + except IndexError: + raise Error('attempted to access stack frame {} out of {}' + .format(idx, len(stack_frames))) + + def get_step_info(self): + thread = self._debugger.CurrentThread + stackframes = thread.StackFrames + + frames = [] + state_frames = [] + + + for idx, sf in enumerate(stackframes): + frame = FrameIR( + function=self._sanitize_function_name(sf.FunctionName), + is_inlined=sf.FunctionName.startswith('[Inline Frame]'), + loc=LocIR(path=None, lineno=None, column=None)) + + fname = frame.function or '' # pylint: disable=no-member + if any(name in fname for name in self.frames_below_main): + break + + + state_frame = StackFrame(function=frame.function, + is_inlined=frame.is_inlined, + watches={}) + + for watch in self.watches: + state_frame.watches[watch] = self.evaluate_expression( + watch, idx) + + + state_frames.append(state_frame) + frames.append(frame) + + loc = LocIR(**self._location) + if frames: + frames[0].loc = loc + state_frames[0].location = SourceLocation(**self._location) + + reason = StopReason.BREAKPOINT + if loc.path is None: # pylint: disable=no-member + reason = StopReason.STEP + + program_state = ProgramState(frames=state_frames) + + return StepIR( + step_index=self.step_index, frames=frames, stop_reason=reason, + program_state=program_state) + + @property + def is_running(self): + return self._mode == VisualStudio.dbgRunMode + + @property + def is_finished(self): + return self._mode == VisualStudio.dbgDesignMode + + @property + def frames_below_main(self): + return [ + '[Inline Frame] invoke_main', '__scrt_common_main_seh', + '__tmainCRTStartup', 'mainCRTStartup' + ] + + def evaluate_expression(self, expression, frame_idx=0) -> ValueIR: + self.set_current_stack_frame(frame_idx) + result = self._debugger.GetExpression(expression) + self.set_current_stack_frame(0) + value = result.Value + + is_optimized_away = any(s in value for s in [ + 'Variable is optimized away and not available', + 'Value is not available, possibly due to optimization', + ]) + + is_irretrievable = any(s in value for s in [ + '???', + '<Unable to read memory>', + ]) + + # an optimized away value is still counted as being able to be + # evaluated. + could_evaluate = (result.IsValidValue or is_optimized_away + or is_irretrievable) + + return ValueIR( + expression=expression, + value=value, + type_name=result.Type, + error_string=None, + is_optimized_away=is_optimized_away, + could_evaluate=could_evaluate, + is_irretrievable=is_irretrievable, + ) diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2015.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2015.py new file mode 100644 index 00000000000..af6edcd2451 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2015.py @@ -0,0 +1,23 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Specializations for the Visual Studio 2015 interface.""" + +from dex.debugger.visualstudio.VisualStudio import VisualStudio + + +class VisualStudio2015(VisualStudio): + @classmethod + def get_name(cls): + return 'Visual Studio 2015' + + @classmethod + def get_option_name(cls): + return 'vs2015' + + @property + def _dte_version(self): + return 'VisualStudio.DTE.14.0' diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2017.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2017.py new file mode 100644 index 00000000000..f2f757546f3 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2017.py @@ -0,0 +1,23 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Specializations for the Visual Studio 2017 interface.""" + +from dex.debugger.visualstudio.VisualStudio import VisualStudio + + +class VisualStudio2017(VisualStudio): + @classmethod + def get_name(cls): + return 'Visual Studio 2017' + + @classmethod + def get_option_name(cls): + return 'vs2017' + + @property + def _dte_version(self): + return 'VisualStudio.DTE.15.0' diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/__init__.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/__init__.py new file mode 100644 index 00000000000..35fefacf22f --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/__init__.py @@ -0,0 +1,9 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from dex.debugger.visualstudio.VisualStudio2015 import VisualStudio2015 +from dex.debugger.visualstudio.VisualStudio2017 import VisualStudio2017 diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/ComInterface.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/ComInterface.py new file mode 100644 index 00000000000..0bce5b533e7 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/ComInterface.py @@ -0,0 +1,119 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Communication via the Windows COM interface.""" + +import inspect +import time +import sys + +# pylint: disable=import-error +import win32com.client as com +import win32api +# pylint: enable=import-error + +from dex.utils.Exceptions import LoadDebuggerException + +_com_error = com.pywintypes.com_error # pylint: disable=no-member + + +def get_file_version(file_): + try: + info = win32api.GetFileVersionInfo(file_, '\\') + ms = info['FileVersionMS'] + ls = info['FileVersionLS'] + return '.'.join( + str(s) for s in [ + win32api.HIWORD(ms), + win32api.LOWORD(ms), + win32api.HIWORD(ls), + win32api.LOWORD(ls) + ]) + except com.pywintypes.error: # pylint: disable=no-member + return 'no versioninfo present' + + +def _handle_com_error(e): + exc = sys.exc_info() + msg = win32api.FormatMessage(e.hresult) + try: + msg = msg.decode('CP1251') + except AttributeError: + pass + msg = msg.strip() + return msg, exc + + +class ComObject(object): + """Wrap a raw Windows COM object in a class that implements auto-retry of + failed calls. + """ + + def __init__(self, raw): + assert not isinstance(raw, ComObject), raw + self.__dict__['raw'] = raw + + def __str__(self): + return self._call(self.raw.__str__) + + def __getattr__(self, key): + if key in self.__dict__: + return self.__dict__[key] + return self._call(self.raw.__getattr__, key) + + def __setattr__(self, key, val): + if key in self.__dict__: + self.__dict__[key] = val + self._call(self.raw.__setattr__, key, val) + + def __getitem__(self, key): + return self._call(self.raw.__getitem__, key) + + def __setitem__(self, key, val): + self._call(self.raw.__setitem__, key, val) + + def __call__(self, *args): + return self._call(self.raw, *args) + + @classmethod + def _call(cls, fn, *args): + """COM calls tend to randomly fail due to thread sync issues. + The Microsoft recommended solution is to set up a message filter object + to automatically retry failed calls, but this seems prohibitively hard + from python, so this is a custom solution to do the same thing. + All COM accesses should go through this function. + """ + ex = AssertionError("this should never be raised!") + + assert (inspect.isfunction(fn) or inspect.ismethod(fn) + or inspect.isbuiltin(fn)), (fn, type(fn)) + retries = ([0] * 50) + ([1] * 5) + for r in retries: + try: + try: + result = fn(*args) + if inspect.ismethod(result) or 'win32com' in str( + result.__class__): + result = ComObject(result) + return result + except _com_error as e: + msg, _ = _handle_com_error(e) + e = WindowsError(msg) # pylint: disable=undefined-variable + raise e + except (AttributeError, TypeError, OSError) as e: + ex = e + time.sleep(r) + raise ex + + +class DTE(ComObject): + def __init__(self, class_string): + try: + super(DTE, self).__init__(com.DispatchEx(class_string)) + except _com_error as e: + msg, exc = _handle_com_error(e) + raise LoadDebuggerException( + '{} [{}]'.format(msg, class_string), orig_exception=exc) diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/__init__.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/__init__.py new file mode 100644 index 00000000000..1194affd891 --- /dev/null +++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/windows/__init__.py @@ -0,0 +1,6 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |