• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# DExTer : Debugging Experience Tester
2# ~~~~~~   ~         ~~         ~   ~~
3#
4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5# See https://llvm.org/LICENSE.txt for license information.
6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7"""Interface for communicating with the Visual Studio debugger via DTE."""
8
9import abc
10import imp
11import os
12import sys
13
14from dex.debugger.DebuggerBase import DebuggerBase
15from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
16from dex.dextIR import StackFrame, SourceLocation, ProgramState
17from dex.utils.Exceptions import Error, LoadDebuggerException
18from dex.utils.ReturnCode import ReturnCode
19
20
21def _load_com_module():
22    try:
23        module_info = imp.find_module(
24            'ComInterface',
25            [os.path.join(os.path.dirname(__file__), 'windows')])
26        return imp.load_module('ComInterface', *module_info)
27    except ImportError as e:
28        raise LoadDebuggerException(e, sys.exc_info())
29
30
31class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta):  # pylint: disable=abstract-method
32
33    # Constants for results of Debugger.CurrentMode
34    # (https://msdn.microsoft.com/en-us/library/envdte.debugger.currentmode.aspx)
35    dbgDesignMode = 1
36    dbgBreakMode = 2
37    dbgRunMode = 3
38
39    def __init__(self, *args):
40        self.com_module = None
41        self._debugger = None
42        self._solution = None
43        self._fn_step = None
44        self._fn_go = None
45        super(VisualStudio, self).__init__(*args)
46
47    def _custom_init(self):
48        try:
49            self._debugger = self._interface.Debugger
50            self._debugger.HexDisplayMode = False
51
52            self._interface.MainWindow.Visible = (
53                self.context.options.show_debugger)
54
55            self._solution = self._interface.Solution
56            self._solution.Create(self.context.working_directory.path,
57                                  'DexterSolution')
58
59            try:
60                self._solution.AddFromFile(self._project_file)
61            except OSError:
62                raise LoadDebuggerException(
63                    'could not debug the specified executable', sys.exc_info())
64
65            self._fn_step = self._debugger.StepInto
66            self._fn_go = self._debugger.Go
67
68        except AttributeError as e:
69            raise LoadDebuggerException(str(e), sys.exc_info())
70
71    def _custom_exit(self):
72        if self._interface:
73            self._interface.Quit()
74
75    @property
76    def _project_file(self):
77        return self.context.options.executable
78
79    @abc.abstractproperty
80    def _dte_version(self):
81        pass
82
83    @property
84    def _location(self):
85        #TODO: Find a better way of determining path, line and column info
86        # that doesn't require reading break points. This method requires
87        # all lines to have a break point on them.
88        bp = self._debugger.BreakpointLastHit
89        return {
90            'path': getattr(bp, 'File', None),
91            'lineno': getattr(bp, 'FileLine', None),
92            'column': getattr(bp, 'FileColumn', None)
93        }
94
95    @property
96    def _mode(self):
97        return self._debugger.CurrentMode
98
99    def _load_interface(self):
100        self.com_module = _load_com_module()
101        return self.com_module.DTE(self._dte_version)
102
103    @property
104    def version(self):
105        try:
106            return self._interface.Version
107        except AttributeError:
108            return None
109
110    def clear_breakpoints(self):
111        for bp in self._debugger.Breakpoints:
112            bp.Delete()
113
114    def _add_breakpoint(self, file_, line):
115        self._debugger.Breakpoints.Add('', file_, line)
116
117    def _add_conditional_breakpoint(self, file_, line, condition):
118        column = 1
119        self._debugger.Breakpoints.Add('', file_, line, column, condition)
120
121    def _delete_conditional_breakpoint(self, file_, line, condition):
122        for bp in self._debugger.Breakpoints:
123            for bound_bp in bp.Children:
124                if (bound_bp.File == file_ and bound_bp.FileLine == line and
125                    bound_bp.Condition == condition):
126                    bp.Delete()
127                    break
128
129    def launch(self):
130        self._fn_go()
131
132    def step(self):
133        self._fn_step()
134
135    def go(self) -> ReturnCode:
136        self._fn_go()
137        return ReturnCode.OK
138
139    def set_current_stack_frame(self, idx: int = 0):
140        thread = self._debugger.CurrentThread
141        stack_frames = thread.StackFrames
142        try:
143            stack_frame = stack_frames[idx]
144            self._debugger.CurrentStackFrame = stack_frame.raw
145        except IndexError:
146            raise Error('attempted to access stack frame {} out of {}'
147                .format(idx, len(stack_frames)))
148
149    def _get_step_info(self, watches, step_index):
150        thread = self._debugger.CurrentThread
151        stackframes = thread.StackFrames
152
153        frames = []
154        state_frames = []
155
156
157        for idx, sf in enumerate(stackframes):
158            frame = FrameIR(
159                function=self._sanitize_function_name(sf.FunctionName),
160                is_inlined=sf.FunctionName.startswith('[Inline Frame]'),
161                loc=LocIR(path=None, lineno=None, column=None))
162
163            fname = frame.function or ''  # pylint: disable=no-member
164            if any(name in fname for name in self.frames_below_main):
165                break
166
167
168            state_frame = StackFrame(function=frame.function,
169                                     is_inlined=frame.is_inlined,
170                                     watches={})
171
172            for watch in watches:
173                state_frame.watches[watch] = self.evaluate_expression(
174                    watch, idx)
175
176
177            state_frames.append(state_frame)
178            frames.append(frame)
179
180        loc = LocIR(**self._location)
181        if frames:
182            frames[0].loc = loc
183            state_frames[0].location = SourceLocation(**self._location)
184
185        reason = StopReason.BREAKPOINT
186        if loc.path is None:  # pylint: disable=no-member
187            reason = StopReason.STEP
188
189        program_state = ProgramState(frames=state_frames)
190
191        return StepIR(
192            step_index=step_index, frames=frames, stop_reason=reason,
193            program_state=program_state)
194
195    @property
196    def is_running(self):
197        return self._mode == VisualStudio.dbgRunMode
198
199    @property
200    def is_finished(self):
201        return self._mode == VisualStudio.dbgDesignMode
202
203    @property
204    def frames_below_main(self):
205        return [
206            '[Inline Frame] invoke_main', '__scrt_common_main_seh',
207            '__tmainCRTStartup', 'mainCRTStartup'
208        ]
209
210    def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
211        self.set_current_stack_frame(frame_idx)
212        result = self._debugger.GetExpression(expression)
213        self.set_current_stack_frame(0)
214        value = result.Value
215
216        is_optimized_away = any(s in value for s in [
217            'Variable is optimized away and not available',
218            'Value is not available, possibly due to optimization',
219        ])
220
221        is_irretrievable = any(s in value for s in [
222            '???',
223            '<Unable to read memory>',
224        ])
225
226        # an optimized away value is still counted as being able to be
227        # evaluated.
228        could_evaluate = (result.IsValidValue or is_optimized_away
229                          or is_irretrievable)
230
231        return ValueIR(
232            expression=expression,
233            value=value,
234            type_name=result.Type,
235            error_string=None,
236            is_optimized_away=is_optimized_away,
237            could_evaluate=could_evaluate,
238            is_irretrievable=is_irretrievable,
239        )
240