summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/debugger/visualstudio
diff options
context:
space:
mode:
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger/visualstudio')
-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
6 files changed, 404 insertions, 0 deletions
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