diff options
Diffstat (limited to 'debuginfo-tests/dexter/dex/debugger/visualstudio')
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 |