You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
7.6 KiB
240 lines
7.6 KiB
4 months ago
|
# 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,
|
||
|
)
|