summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/debugger
diff options
context:
space:
mode:
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger')
-rw-r--r--debuginfo-tests/dexter/dex/debugger/DebuggerBase.py227
-rw-r--r--debuginfo-tests/dexter/dex/debugger/Debuggers.py299
-rw-r--r--debuginfo-tests/dexter/dex/debugger/__init__.py8
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/README.md60
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/__init__.py19
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/breakpoint.py88
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/client.py185
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/control.py405
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py163
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/probe_process.py80
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/setup.py185
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/symbols.py499
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/symgroup.py98
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/sysobjs.py200
-rw-r--r--debuginfo-tests/dexter/dex/debugger/dbgeng/utils.py47
-rw-r--r--debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py244
-rw-r--r--debuginfo-tests/dexter/dex/debugger/lldb/__init__.py8
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py224
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2015.py23
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio2017.py23
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/__init__.py9
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/windows/ComInterface.py119
-rw-r--r--debuginfo-tests/dexter/dex/debugger/visualstudio/windows/__init__.py6
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
OpenPOWER on IntegriCloud