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 16 17class EarlyExitCondition(object): 18 def __init__(self, on_line, hit_count, expression, values): 19 self.on_line = on_line 20 self.hit_count = hit_count 21 self.expression = expression 22 self.values = values 23 24class DefaultController(DebuggerControllerBase): 25 def __init__(self, context, step_collection): 26 self.context = context 27 self.step_collection = step_collection 28 self.source_files = self.context.options.source_files 29 self.watches = set() 30 self.step_index = 0 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): 77 self.step_collection.debugger = self.debugger.debugger_info 78 self._break_point_all_lines() 79 self.debugger.launch() 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 max_steps = self.context.options.max_steps 86 for _ in range(max_steps): 87 while self.debugger.is_running: 88 pass 89 90 if self.debugger.is_finished: 91 break 92 93 self.step_index += 1 94 step_info = self.debugger.get_step_info(self.watches, self.step_index) 95 96 if step_info.current_frame: 97 update_step_watches(step_info, self.watches, self.step_collection.commands) 98 self.step_collection.new_step(self.context, step_info) 99 if self._should_exit(early_exit_conditions, step_info.current_frame.loc.lineno): 100 break 101 102 if in_source_file(self.source_files, step_info): 103 self.debugger.step() 104 else: 105 self.debugger.go() 106 107 time.sleep(self.context.options.pause_between_steps) 108 else: 109 raise DebuggerException( 110 'maximum number of steps reached ({})'.format(max_steps)) 111