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.source_files = context.options.source_files 27 self.watches = set() 28 self.step_index = 0 29 super(DefaultController, self).__init__(context, step_collection) 30 31 def _break_point_all_lines(self): 32 for s in self.context.options.source_files: 33 with open(s, 'r') as fp: 34 num_lines = len(fp.readlines()) 35 for line in range(1, num_lines + 1): 36 try: 37 self.debugger.add_breakpoint(s, line) 38 except DebuggerException: 39 raise LoadDebuggerException(DebuggerException.msg) 40 41 def _get_early_exit_conditions(self): 42 commands = self.step_collection.commands 43 early_exit_conditions = [] 44 if 'DexFinishTest' in commands: 45 finish_commands = commands['DexFinishTest'] 46 for fc in finish_commands: 47 condition = EarlyExitCondition(on_line=fc.on_line, 48 hit_count=fc.hit_count, 49 expression=fc.expression, 50 values=fc.values) 51 early_exit_conditions.append(condition) 52 return early_exit_conditions 53 54 def _should_exit(self, early_exit_conditions, line_no): 55 for condition in early_exit_conditions: 56 if condition.on_line == line_no: 57 exit_condition_hit = condition.expression is None 58 if condition.expression is not None: 59 # For the purposes of consistent behaviour with the 60 # Conditional Controller, check equality in the debugger 61 # rather than in python (as the two can differ). 62 for value in condition.values: 63 expr_val = self.debugger.evaluate_expression(f'({condition.expression}) == ({value})') 64 if expr_val.value == 'true': 65 exit_condition_hit = True 66 break 67 if exit_condition_hit: 68 if condition.hit_count <= 0: 69 return True 70 else: 71 condition.hit_count -= 1 72 return False 73 74 75 def _run_debugger_custom(self, cmdline): 76 self.step_collection.debugger = self.debugger.debugger_info 77 self._break_point_all_lines() 78 self.debugger.launch(cmdline) 79 80 for command_obj in chain.from_iterable(self.step_collection.commands.values()): 81 self.watches.update(command_obj.get_watches()) 82 early_exit_conditions = self._get_early_exit_conditions() 83 84 max_steps = self.context.options.max_steps 85 for _ in range(max_steps): 86 while self.debugger.is_running: 87 pass 88 89 if self.debugger.is_finished: 90 break 91 92 self.step_index += 1 93 step_info = self.debugger.get_step_info(self.watches, self.step_index) 94 95 if step_info.current_frame: 96 update_step_watches(step_info, self.watches, self.step_collection.commands) 97 self.step_collection.new_step(self.context, step_info) 98 if self._should_exit(early_exit_conditions, step_info.current_frame.loc.lineno): 99 break 100 101 if in_source_file(self.source_files, step_info): 102 self.debugger.step() 103 else: 104 self.debugger.go() 105 106 time.sleep(self.context.options.pause_between_steps) 107 else: 108 raise DebuggerException( 109 'maximum number of steps reached ({})'.format(max_steps)) 110