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 8import sys 9import os 10import platform 11 12from dex.debugger.DebuggerBase import DebuggerBase, watch_is_active 13from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR 14from dex.dextIR import ProgramState, StackFrame, SourceLocation 15from dex.utils.Exceptions import DebuggerException, LoadDebuggerException 16from dex.utils.ReturnCode import ReturnCode 17 18if platform.system() == "Windows": 19 # Don't load on linux; _load_interface will croak before any names are used. 20 from . import setup 21 from . import probe_process 22 from . import breakpoint 23 24 25class DbgEng(DebuggerBase): 26 def __init__(self, context, *args): 27 self.breakpoints = [] 28 self.running = False 29 self.finished = False 30 self.step_info = None 31 super(DbgEng, self).__init__(context, *args) 32 33 def _custom_init(self): 34 try: 35 res = setup.setup_everything(self.context.options.executable) 36 self.client = res 37 self.running = True 38 except Exception as e: 39 raise Exception("Failed to start debuggee: {}".format(e)) 40 41 def _custom_exit(self): 42 setup.cleanup(self.client) 43 44 def _load_interface(self): 45 arch = platform.architecture()[0] 46 machine = platform.machine() 47 if arch == "32bit" and machine == "AMD64": 48 # This python process is 32 bits, but is sitting on a 64 bit machine. 49 # Bad things may happen, don't support it. 50 raise LoadDebuggerException( 51 "Can't run Dexter dbgeng on 32 bit python in a 64 bit environment" 52 ) 53 54 if platform.system() != "Windows": 55 raise LoadDebuggerException("DbgEng supports Windows only") 56 57 # Otherwise, everything was imported earlier 58 59 @classmethod 60 def get_name(cls): 61 return "dbgeng" 62 63 @classmethod 64 def get_option_name(cls): 65 return "dbgeng" 66 67 @property 68 def frames_below_main(self): 69 return [] 70 71 @property 72 def version(self): 73 # I don't believe there's a well defined DbgEng version, outside of the 74 # version of Windows being used. 75 return "1" 76 77 def clear_breakpoints(self): 78 for x in self.breakpoints: 79 x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED) 80 self.client.Control.RemoveBreakpoint(x) 81 82 def _add_breakpoint(self, file_, line): 83 # Breakpoint setting/deleting is not supported by dbgeng at this moment 84 # but is something that should be considered in the future. 85 # TODO: this method is called in the DefaultController but has no effect. 86 pass 87 88 def _add_conditional_breakpoint(self, file_, line, condition): 89 # breakpoint setting/deleting is not supported by dbgeng at this moment 90 # but is something that should be considered in the future. 91 raise NotImplementedError( 92 "add_conditional_breakpoint is not yet implemented by dbgeng" 93 ) 94 95 def get_triggered_breakpoint_ids(self): 96 raise NotImplementedError( 97 "get_triggered_breakpoint_ids is not yet implemented by dbgeng" 98 ) 99 100 def delete_breakpoints(self, ids): 101 # breakpoint setting/deleting is not supported by dbgeng at this moment 102 # but is something that should be considered in the future. 103 raise NotImplementedError( 104 "delete_conditional_breakpoint is not yet implemented by dbgeng" 105 ) 106 107 def launch(self, cmdline): 108 assert ( 109 len(cmdline) == 0 and not self.context.options.target_run_args 110 ), "Command lines unimplemented for dbgeng right now" 111 # We are, by this point, already launched. 112 self.step_info = probe_process.probe_state(self.client) 113 114 def step(self): 115 res = setup.step_once(self.client) 116 if not res: 117 self.finished = True 118 self.step_info = res 119 120 def go(self): 121 # FIXME: running freely doesn't seem to reliably stop when back in a 122 # relevant source file -- this is likely to be a problem when setting 123 # breakpoints. Until that's fixed, single step instead of running 124 # freely. This isn't very efficient, but at least makes progress. 125 self.step() 126 127 def _get_step_info(self, watches, step_index): 128 frames = self.step_info 129 state_frames = [] 130 131 # For now assume the base function is the... function, ignoring 132 # inlining. 133 dex_frames = [] 134 for i, x in enumerate(frames): 135 # XXX Might be able to get columns out through 136 # GetSourceEntriesByOffset, not a priority now 137 loc = LocIR(path=x.source_file, lineno=x.line_no, column=0) 138 new_frame = FrameIR(function=x.function_name, is_inlined=False, loc=loc) 139 dex_frames.append(new_frame) 140 141 state_frame = StackFrame( 142 function=new_frame.function, 143 is_inlined=new_frame.is_inlined, 144 location=SourceLocation(path=x.source_file, lineno=x.line_no, column=0), 145 watches={}, 146 ) 147 for expr in map( 148 # Filter out watches that are not active in the current frame, 149 # and then evaluate all the active watches. 150 lambda watch_info, idx=i: self.evaluate_expression( 151 watch_info.expression, idx 152 ), 153 filter( 154 lambda watch_info, idx=i, line_no=loc.lineno, path=loc.path: watch_is_active( 155 watch_info, path, idx, line_no 156 ), 157 watches, 158 ), 159 ): 160 state_frame.watches[expr.expression] = expr 161 state_frames.append(state_frame) 162 163 return StepIR( 164 step_index=step_index, 165 frames=dex_frames, 166 stop_reason=StopReason.STEP, 167 program_state=ProgramState(state_frames), 168 ) 169 170 @property 171 def is_running(self): 172 return False # We're never free-running 173 174 @property 175 def is_finished(self): 176 return self.finished 177 178 def evaluate_expression(self, expression, frame_idx=0): 179 # XXX: cdb insists on using '->' to examine fields of structures, 180 # as it appears to reserve '.' for other purposes. 181 fixed_expr = expression.replace(".", "->") 182 183 orig_scope_idx = self.client.Symbols.GetCurrentScopeFrameIndex() 184 self.client.Symbols.SetScopeFrameByIndex(frame_idx) 185 186 res = self.client.Control.Evaluate(fixed_expr) 187 if res is not None: 188 result, typename = self.client.Control.Evaluate(fixed_expr) 189 could_eval = True 190 else: 191 result, typename = (None, None) 192 could_eval = False 193 194 self.client.Symbols.SetScopeFrameByIndex(orig_scope_idx) 195 196 return ValueIR( 197 expression=expression, 198 value=str(result), 199 type_name=typename, 200 error_string="", 201 could_evaluate=could_eval, 202 is_optimized_away=False, 203 is_irretrievable=not could_eval, 204 ) 205