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 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 24class DbgEng(DebuggerBase): 25 def __init__(self, context, *args): 26 self.breakpoints = [] 27 self.running = False 28 self.finished = False 29 self.step_info = None 30 super(DbgEng, self).__init__(context, *args) 31 32 def _custom_init(self): 33 try: 34 res = setup.setup_everything(self.context.options.executable) 35 self.client = res 36 self.running = True 37 except Exception as e: 38 raise Exception('Failed to start debuggee: {}'.format(e)) 39 40 def _custom_exit(self): 41 setup.cleanup(self.client) 42 43 def _load_interface(self): 44 arch = platform.architecture()[0] 45 machine = platform.machine() 46 if arch == '32bit' and machine == 'AMD64': 47 # This python process is 32 bits, but is sitting on a 64 bit machine. 48 # Bad things may happen, don't support it. 49 raise LoadDebuggerException('Can\'t run Dexter dbgeng on 32 bit python in a 64 bit environment') 50 51 if platform.system() != 'Windows': 52 raise LoadDebuggerException('DbgEng supports Windows only') 53 54 # Otherwise, everything was imported earlier 55 56 @classmethod 57 def get_name(cls): 58 return 'dbgeng' 59 60 @classmethod 61 def get_option_name(cls): 62 return 'dbgeng' 63 64 @property 65 def frames_below_main(self): 66 return [] 67 68 @property 69 def version(self): 70 # I don't believe there's a well defined DbgEng version, outside of the 71 # version of Windows being used. 72 return "1" 73 74 def clear_breakpoints(self): 75 for x in self.breakpoints: 76 x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED) 77 self.client.Control.RemoveBreakpoint(x) 78 79 def _add_breakpoint(self, file_, line): 80 # Breakpoint setting/deleting is not supported by dbgeng at this moment 81 # but is something that should be considered in the future. 82 # TODO: this method is called in the DefaultController but has no effect. 83 pass 84 85 def _add_conditional_breakpoint(self, file_, line, condition): 86 # breakpoint setting/deleting is not supported by dbgeng at this moment 87 # but is something that should be considered in the future. 88 raise NotImplementedError('add_conditional_breakpoint is not yet implemented by dbgeng') 89 90 def _delete_conditional_breakpoint(self, file_, line, condition): 91 # breakpoint setting/deleting is not supported by dbgeng at this moment 92 # but is something that should be considered in the future. 93 raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng') 94 95 def launch(self): 96 # We are, by this point, already launched. 97 self.step_info = probe_process.probe_state(self.client) 98 99 def step(self): 100 res = setup.step_once(self.client) 101 if not res: 102 self.finished = True 103 self.step_info = res 104 105 def go(self): 106 # We never go -- we always single step. 107 pass 108 109 def _get_step_info(self, watches, step_index): 110 frames = self.step_info 111 state_frames = [] 112 113 # For now assume the base function is the... function, ignoring 114 # inlining. 115 dex_frames = [] 116 for i, x in enumerate(frames): 117 # XXX Might be able to get columns out through 118 # GetSourceEntriesByOffset, not a priority now 119 loc = LocIR(path=x.source_file, lineno=x.line_no, column=0) 120 new_frame = FrameIR(function=x.function_name, is_inlined=False, loc=loc) 121 dex_frames.append(new_frame) 122 123 state_frame = StackFrame(function=new_frame.function, 124 is_inlined=new_frame.is_inlined, 125 location=SourceLocation(path=x.source_file, 126 lineno=x.line_no, 127 column=0), 128 watches={}) 129 for expr in map( 130 lambda watch, idx=i: self.evaluate_expression(watch, idx), 131 watches): 132 state_frame.watches[expr.expression] = expr 133 state_frames.append(state_frame) 134 135 return StepIR( 136 step_index=step_index, frames=dex_frames, 137 stop_reason=StopReason.STEP, 138 program_state=ProgramState(state_frames)) 139 140 @property 141 def is_running(self): 142 return False # We're never free-running 143 144 @property 145 def is_finished(self): 146 return self.finished 147 148 def evaluate_expression(self, expression, frame_idx=0): 149 # XXX: cdb insists on using '->' to examine fields of structures, 150 # as it appears to reserve '.' for other purposes. 151 fixed_expr = expression.replace('.', '->') 152 153 orig_scope_idx = self.client.Symbols.GetCurrentScopeFrameIndex() 154 self.client.Symbols.SetScopeFrameByIndex(frame_idx) 155 156 res = self.client.Control.Evaluate(fixed_expr) 157 if res is not None: 158 result, typename = self.client.Control.Evaluate(fixed_expr) 159 could_eval = True 160 else: 161 result, typename = (None, None) 162 could_eval = False 163 164 self.client.Symbols.SetScopeFrameByIndex(orig_scope_idx) 165 166 return ValueIR( 167 expression=expression, 168 value=str(result), 169 type_name=typename, 170 error_string="", 171 could_evaluate=could_eval, 172 is_optimized_away=False, 173 is_irretrievable=not could_eval) 174