11364750dSJames Henderson# DExTer : Debugging Experience Tester
21364750dSJames Henderson# ~~~~~~   ~         ~~         ~   ~~
31364750dSJames Henderson#
41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information.
61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
71364750dSJames Henderson"""Default class for controlling debuggers."""
81364750dSJames Henderson
91364750dSJames Hendersonfrom itertools import chain
101364750dSJames Hendersonimport os
111364750dSJames Hendersonimport time
121364750dSJames Henderson
13*f98ee40fSTobias Hietafrom dex.debugger.DebuggerControllers.DebuggerControllerBase import (
14*f98ee40fSTobias Hieta    DebuggerControllerBase,
15*f98ee40fSTobias Hieta)
16*f98ee40fSTobias Hietafrom dex.debugger.DebuggerControllers.ControllerHelpers import (
17*f98ee40fSTobias Hieta    in_source_file,
18*f98ee40fSTobias Hieta    update_step_watches,
19*f98ee40fSTobias Hieta)
201364750dSJames Hendersonfrom dex.utils.Exceptions import DebuggerException, LoadDebuggerException
21ee5617dcSStephen Tozerfrom dex.utils.Timeout import Timeout
221364750dSJames Henderson
23*f98ee40fSTobias Hieta
246cf69179SStephen Tozerclass EarlyExitCondition(object):
256cf69179SStephen Tozer    def __init__(self, on_line, hit_count, expression, values):
266cf69179SStephen Tozer        self.on_line = on_line
276cf69179SStephen Tozer        self.hit_count = hit_count
286cf69179SStephen Tozer        self.expression = expression
296cf69179SStephen Tozer        self.values = values
306cf69179SStephen Tozer
31*f98ee40fSTobias Hieta
321364750dSJames Hendersonclass DefaultController(DebuggerControllerBase):
331364750dSJames Henderson    def __init__(self, context, step_collection):
343a094d8bSJeremy Morse        self.source_files = context.options.source_files
351364750dSJames Henderson        self.watches = set()
361364750dSJames Henderson        self.step_index = 0
373a094d8bSJeremy Morse        super(DefaultController, self).__init__(context, step_collection)
381364750dSJames Henderson
391364750dSJames Henderson    def _break_point_all_lines(self):
401364750dSJames Henderson        for s in self.context.options.source_files:
41*f98ee40fSTobias Hieta            with open(s, "r") as fp:
421364750dSJames Henderson                num_lines = len(fp.readlines())
431364750dSJames Henderson            for line in range(1, num_lines + 1):
441364750dSJames Henderson                try:
451364750dSJames Henderson                    self.debugger.add_breakpoint(s, line)
461364750dSJames Henderson                except DebuggerException:
471364750dSJames Henderson                    raise LoadDebuggerException(DebuggerException.msg)
481364750dSJames Henderson
496cf69179SStephen Tozer    def _get_early_exit_conditions(self):
506cf69179SStephen Tozer        commands = self.step_collection.commands
516cf69179SStephen Tozer        early_exit_conditions = []
52*f98ee40fSTobias Hieta        if "DexFinishTest" in commands:
53*f98ee40fSTobias Hieta            finish_commands = commands["DexFinishTest"]
546cf69179SStephen Tozer            for fc in finish_commands:
55*f98ee40fSTobias Hieta                condition = EarlyExitCondition(
56*f98ee40fSTobias Hieta                    on_line=fc.on_line,
576cf69179SStephen Tozer                    hit_count=fc.hit_count,
586cf69179SStephen Tozer                    expression=fc.expression,
59*f98ee40fSTobias Hieta                    values=fc.values,
60*f98ee40fSTobias Hieta                )
616cf69179SStephen Tozer                early_exit_conditions.append(condition)
626cf69179SStephen Tozer        return early_exit_conditions
636cf69179SStephen Tozer
646cf69179SStephen Tozer    def _should_exit(self, early_exit_conditions, line_no):
656cf69179SStephen Tozer        for condition in early_exit_conditions:
666cf69179SStephen Tozer            if condition.on_line == line_no:
676cf69179SStephen Tozer                exit_condition_hit = condition.expression is None
686cf69179SStephen Tozer                if condition.expression is not None:
696cf69179SStephen Tozer                    # For the purposes of consistent behaviour with the
706cf69179SStephen Tozer                    # Conditional Controller, check equality in the debugger
716cf69179SStephen Tozer                    # rather than in python (as the two can differ).
726cf69179SStephen Tozer                    for value in condition.values:
73*f98ee40fSTobias Hieta                        expr_val = self.debugger.evaluate_expression(
74*f98ee40fSTobias Hieta                            f"({condition.expression}) == ({value})"
75*f98ee40fSTobias Hieta                        )
76*f98ee40fSTobias Hieta                        if expr_val.value == "true":
776cf69179SStephen Tozer                            exit_condition_hit = True
786cf69179SStephen Tozer                            break
796cf69179SStephen Tozer                if exit_condition_hit:
806cf69179SStephen Tozer                    if condition.hit_count <= 0:
816cf69179SStephen Tozer                        return True
826cf69179SStephen Tozer                    else:
836cf69179SStephen Tozer                        condition.hit_count -= 1
846cf69179SStephen Tozer        return False
856cf69179SStephen Tozer
863a094d8bSJeremy Morse    def _run_debugger_custom(self, cmdline):
871364750dSJames Henderson        self.step_collection.debugger = self.debugger.debugger_info
881364750dSJames Henderson        self._break_point_all_lines()
893a094d8bSJeremy Morse        self.debugger.launch(cmdline)
901364750dSJames Henderson        for command_obj in chain.from_iterable(self.step_collection.commands.values()):
911364750dSJames Henderson            self.watches.update(command_obj.get_watches())
926cf69179SStephen Tozer        early_exit_conditions = self._get_early_exit_conditions()
93ee5617dcSStephen Tozer        timed_out = False
94ee5617dcSStephen Tozer        total_timeout = Timeout(self.context.options.timeout_total)
951364750dSJames Henderson        max_steps = self.context.options.max_steps
961364750dSJames Henderson        for _ in range(max_steps):
97ee5617dcSStephen Tozer            breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint)
98ee5617dcSStephen Tozer            while self.debugger.is_running and not timed_out:
99ee5617dcSStephen Tozer                # Check to see whether we've timed out while we're waiting.
100ee5617dcSStephen Tozer                if total_timeout.timed_out():
101*f98ee40fSTobias Hieta                    self.context.logger.error(
102*f98ee40fSTobias Hieta                        "Debugger session has been "
103*f98ee40fSTobias Hieta                        f"running for {total_timeout.elapsed}s, timeout reached!"
104*f98ee40fSTobias Hieta                    )
105ee5617dcSStephen Tozer                    timed_out = True
106ee5617dcSStephen Tozer                if breakpoint_timeout.timed_out():
107*f98ee40fSTobias Hieta                    self.context.logger.error(
108*f98ee40fSTobias Hieta                        f"Debugger session has not "
109*f98ee40fSTobias Hieta                        f"hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout "
110*f98ee40fSTobias Hieta                        "reached!"
111*f98ee40fSTobias Hieta                    )
112ee5617dcSStephen Tozer                    timed_out = True
113ee5617dcSStephen Tozer
114ee5617dcSStephen Tozer            if timed_out or self.debugger.is_finished:
1151364750dSJames Henderson                break
1161364750dSJames Henderson
1171364750dSJames Henderson            self.step_index += 1
1181364750dSJames Henderson            step_info = self.debugger.get_step_info(self.watches, self.step_index)
1191364750dSJames Henderson
1201364750dSJames Henderson            if step_info.current_frame:
121*f98ee40fSTobias Hieta                update_step_watches(
122*f98ee40fSTobias Hieta                    step_info, self.watches, self.step_collection.commands
123*f98ee40fSTobias Hieta                )
1241364750dSJames Henderson                self.step_collection.new_step(self.context, step_info)
125*f98ee40fSTobias Hieta                if self._should_exit(
126*f98ee40fSTobias Hieta                    early_exit_conditions, step_info.current_frame.loc.lineno
127*f98ee40fSTobias Hieta                ):
1286cf69179SStephen Tozer                    break
1291364750dSJames Henderson
1301364750dSJames Henderson            if in_source_file(self.source_files, step_info):
1311364750dSJames Henderson                self.debugger.step()
1321364750dSJames Henderson            else:
1331364750dSJames Henderson                self.debugger.go()
1341364750dSJames Henderson
1351364750dSJames Henderson            time.sleep(self.context.options.pause_between_steps)
1361364750dSJames Henderson        else:
1371364750dSJames Henderson            raise DebuggerException(
138*f98ee40fSTobias Hieta                "maximum number of steps reached ({})".format(max_steps)
139*f98ee40fSTobias Hieta            )
140