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