xref: /llvm-project/cross-project-tests/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
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