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