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"""Default class for controlling debuggers.""" 8 9from itertools import chain 10import os 11import time 12 13from dex.debugger.DebuggerControllers.DebuggerControllerBase import ( 14 DebuggerControllerBase, 15) 16from dex.debugger.DebuggerControllers.ControllerHelpers import ( 17 in_source_file, 18 update_step_watches, 19) 20from dex.utils.Exceptions import DebuggerException, LoadDebuggerException 21from dex.utils.Timeout import Timeout 22 23 24class EarlyExitCondition(object): 25 def __init__(self, on_line, hit_count, expression, values): 26 self.on_line = on_line 27 self.hit_count = hit_count 28 self.expression = expression 29 self.values = values 30 31 32class DefaultController(DebuggerControllerBase): 33 def __init__(self, context, step_collection): 34 self.source_files = context.options.source_files 35 self.watches = set() 36 self.step_index = 0 37 super(DefaultController, self).__init__(context, step_collection) 38 39 def _break_point_all_lines(self): 40 for s in self.context.options.source_files: 41 with open(s, "r") as fp: 42 num_lines = len(fp.readlines()) 43 for line in range(1, num_lines + 1): 44 try: 45 self.debugger.add_breakpoint(s, line) 46 except DebuggerException: 47 raise LoadDebuggerException(DebuggerException.msg) 48 49 def _get_early_exit_conditions(self): 50 commands = self.step_collection.commands 51 early_exit_conditions = [] 52 if "DexFinishTest" in commands: 53 finish_commands = commands["DexFinishTest"] 54 for fc in finish_commands: 55 condition = EarlyExitCondition( 56 on_line=fc.on_line, 57 hit_count=fc.hit_count, 58 expression=fc.expression, 59 values=fc.values, 60 ) 61 early_exit_conditions.append(condition) 62 return early_exit_conditions 63 64 def _should_exit(self, early_exit_conditions, line_no): 65 for condition in early_exit_conditions: 66 if condition.on_line == line_no: 67 exit_condition_hit = condition.expression is None 68 if condition.expression is not None: 69 # For the purposes of consistent behaviour with the 70 # Conditional Controller, check equality in the debugger 71 # rather than in python (as the two can differ). 72 for value in condition.values: 73 expr_val = self.debugger.evaluate_expression( 74 f"({condition.expression}) == ({value})" 75 ) 76 if expr_val.value == "true": 77 exit_condition_hit = True 78 break 79 if exit_condition_hit: 80 if condition.hit_count <= 0: 81 return True 82 else: 83 condition.hit_count -= 1 84 return False 85 86 def _run_debugger_custom(self, cmdline): 87 self.step_collection.debugger = self.debugger.debugger_info 88 self._break_point_all_lines() 89 self.debugger.launch(cmdline) 90 91 for command_obj in chain.from_iterable(self.step_collection.commands.values()): 92 self.watches.update(command_obj.get_watches()) 93 early_exit_conditions = self._get_early_exit_conditions() 94 95 timed_out = False 96 total_timeout = Timeout(self.context.options.timeout_total) 97 max_steps = self.context.options.max_steps 98 for _ in range(max_steps): 99 breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) 100 while self.debugger.is_running and not timed_out: 101 # Check to see whether we've timed out while we're waiting. 102 if total_timeout.timed_out(): 103 self.context.logger.error( 104 "Debugger session has been " 105 f"running for {total_timeout.elapsed}s, timeout reached!" 106 ) 107 timed_out = True 108 if breakpoint_timeout.timed_out(): 109 self.context.logger.error( 110 f"Debugger session has not " 111 f"hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout " 112 "reached!" 113 ) 114 timed_out = True 115 116 if timed_out or self.debugger.is_finished: 117 break 118 119 self.step_index += 1 120 step_info = self.debugger.get_step_info(self.watches, self.step_index) 121 122 if step_info.current_frame: 123 update_step_watches( 124 step_info, self.watches, self.step_collection.commands 125 ) 126 self.step_collection.new_step(self.context, step_info) 127 if self._should_exit( 128 early_exit_conditions, step_info.current_frame.loc.lineno 129 ): 130 break 131 132 if in_source_file(self.source_files, step_info): 133 self.debugger.step() 134 else: 135 self.debugger.go() 136 137 time.sleep(self.context.options.pause_between_steps) 138 else: 139 raise DebuggerException( 140 "maximum number of steps reached ({})".format(max_steps) 141 ) 142