| # 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): |
| #TODO: Find a better way of determining path, line and column info |
| # that doesn't require reading break points. This method requires |
| # all lines to have a break point on them. |
| 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 _add_conditional_breakpoint(self, file_, line, condition): |
| column = 1 |
| self._debugger.Breakpoints.Add('', file_, line, column, condition) |
| |
| def _delete_conditional_breakpoint(self, file_, line, condition): |
| for bp in self._debugger.Breakpoints: |
| for bound_bp in bp.Children: |
| if (bound_bp.File == file_ and bound_bp.FileLine == line and |
| bound_bp.Condition == condition): |
| bp.Delete() |
| break |
| |
| def launch(self): |
| self._fn_go() |
| |
| 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, watches, step_index): |
| 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 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=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, |
| ) |