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