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 for command_obj in chain.from_iterable(self.step_collection.commands.values()): 91 self.watches.update(command_obj.get_watches()) 92 early_exit_conditions = self._get_early_exit_conditions() 93 timed_out = False 94 total_timeout = Timeout(self.context.options.timeout_total) 95 max_steps = self.context.options.max_steps 96 for _ in range(max_steps): 97 breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) 98 while self.debugger.is_running and not timed_out: 99 # Check to see whether we've timed out while we're waiting. 100 if total_timeout.timed_out(): 101 self.context.logger.error( 102 "Debugger session has been " 103 f"running for {total_timeout.elapsed}s, timeout reached!" 104 ) 105 timed_out = True 106 if breakpoint_timeout.timed_out(): 107 self.context.logger.error( 108 f"Debugger session has not " 109 f"hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout " 110 "reached!" 111 ) 112 timed_out = True 113 114 if timed_out or self.debugger.is_finished: 115 break 116 117 self.step_index += 1 118 step_info = self.debugger.get_step_info(self.watches, self.step_index) 119 120 if step_info.current_frame: 121 update_step_watches( 122 step_info, self.watches, self.step_collection.commands 123 ) 124 self.step_collection.new_step(self.context, step_info) 125 if self._should_exit( 126 early_exit_conditions, step_info.current_frame.loc.lineno 127 ): 128 break 129 130 if in_source_file(self.source_files, step_info): 131 self.debugger.step() 132 else: 133 self.debugger.go() 134 135 time.sleep(self.context.options.pause_between_steps) 136 else: 137 raise DebuggerException( 138 "maximum number of steps reached ({})".format(max_steps) 139 ) 140