# DExTer : Debugging Experience Tester # ~~~~~~ ~ ~~ ~ ~~ # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """Default class for controlling debuggers.""" from itertools import chain import os import time from dex.debugger.DebuggerControllers.DebuggerControllerBase import ( DebuggerControllerBase, ) from dex.debugger.DebuggerControllers.ControllerHelpers import ( in_source_file, update_step_watches, ) from dex.utils.Exceptions import DebuggerException, LoadDebuggerException from dex.utils.Timeout import Timeout class EarlyExitCondition(object): def __init__(self, on_line, hit_count, expression, values): self.on_line = on_line self.hit_count = hit_count self.expression = expression self.values = values class DefaultController(DebuggerControllerBase): def __init__(self, context, step_collection): self.source_files = context.options.source_files self.watches = set() self.step_index = 0 super(DefaultController, self).__init__(context, step_collection) def _break_point_all_lines(self): for s in self.context.options.source_files: with open(s, "r") as fp: num_lines = len(fp.readlines()) for line in range(1, num_lines + 1): try: self.debugger.add_breakpoint(s, line) except DebuggerException: raise LoadDebuggerException(DebuggerException.msg) def _get_early_exit_conditions(self): commands = self.step_collection.commands early_exit_conditions = [] if "DexFinishTest" in commands: finish_commands = commands["DexFinishTest"] for fc in finish_commands: condition = EarlyExitCondition( on_line=fc.on_line, hit_count=fc.hit_count, expression=fc.expression, values=fc.values, ) early_exit_conditions.append(condition) return early_exit_conditions def _should_exit(self, early_exit_conditions, line_no): for condition in early_exit_conditions: if condition.on_line == line_no: exit_condition_hit = condition.expression is None if condition.expression is not None: # For the purposes of consistent behaviour with the # Conditional Controller, check equality in the debugger # rather than in python (as the two can differ). for value in condition.values: expr_val = self.debugger.evaluate_expression( f"({condition.expression}) == ({value})" ) if expr_val.value == "true": exit_condition_hit = True break if exit_condition_hit: if condition.hit_count <= 0: return True else: condition.hit_count -= 1 return False def _run_debugger_custom(self, cmdline): self.step_collection.debugger = self.debugger.debugger_info self._break_point_all_lines() self.debugger.launch(cmdline) for command_obj in chain.from_iterable(self.step_collection.commands.values()): self.watches.update(command_obj.get_watches()) early_exit_conditions = self._get_early_exit_conditions() timed_out = False total_timeout = Timeout(self.context.options.timeout_total) max_steps = self.context.options.max_steps for _ in range(max_steps): breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) while self.debugger.is_running and not timed_out: # Check to see whether we've timed out while we're waiting. if total_timeout.timed_out(): self.context.logger.error( "Debugger session has been " f"running for {total_timeout.elapsed}s, timeout reached!" ) timed_out = True if breakpoint_timeout.timed_out(): self.context.logger.error( f"Debugger session has not " f"hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout " "reached!" ) timed_out = True if timed_out or self.debugger.is_finished: break self.step_index += 1 step_info = self.debugger.get_step_info(self.watches, self.step_index) if step_info.current_frame: update_step_watches( step_info, self.watches, self.step_collection.commands ) self.step_collection.new_step(self.context, step_info) if self._should_exit( early_exit_conditions, step_info.current_frame.loc.lineno ): break if in_source_file(self.source_files, step_info): self.debugger.step() else: self.debugger.go() time.sleep(self.context.options.pause_between_steps) else: raise DebuggerException( "maximum number of steps reached ({})".format(max_steps) )