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