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"""Conditional Controller Class for DExTer.-""" 8 9 10import os 11import time 12from collections import defaultdict 13from itertools import chain 14 15from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches 16from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase 17from dex.debugger.DebuggerBase import DebuggerBase 18from dex.utils.Exceptions import DebuggerException 19 20 21class BreakpointRange: 22 """A range of breakpoints and a set of conditions. 23 24 The leading breakpoint (on line `range_from`) is always active. 25 26 When the leading breakpoint is hit the trailing range should be activated 27 when `expression` evaluates to any value in `values`. If there are no 28 conditions (`expression` is None) then the trailing breakpoint range should 29 always be activated upon hitting the leading breakpoint. 30 31 Args: 32 expression: None for no conditions, or a str expression to compare 33 against `values`. 34 35 hit_count: None for no limit, or int to set the number of times the 36 leading breakpoint is triggered before it is removed. 37 """ 38 39 def __init__(self, expression: str, path: str, range_from: int, range_to: int, 40 values: list, hit_count: int, finish_on_remove: bool): 41 self.expression = expression 42 self.path = path 43 self.range_from = range_from 44 self.range_to = range_to 45 self.conditional_values = values 46 self.max_hit_count = hit_count 47 self.current_hit_count = 0 48 self.finish_on_remove = finish_on_remove 49 50 def has_conditions(self): 51 return self.expression != None 52 53 def get_conditional_expression_list(self): 54 conditional_list = [] 55 for value in self.conditional_values: 56 # (<expression>) == (<value>) 57 conditional_expression = '({}) == ({})'.format(self.expression, value) 58 conditional_list.append(conditional_expression) 59 return conditional_list 60 61 def add_hit(self): 62 self.current_hit_count += 1 63 64 def should_be_removed(self): 65 if self.max_hit_count == None: 66 return False 67 return self.current_hit_count >= self.max_hit_count 68 69 70class ConditionalController(DebuggerControllerBase): 71 def __init__(self, context, step_collection): 72 self._bp_ranges = None 73 self._watches = set() 74 self._step_index = 0 75 self._pause_between_steps = context.options.pause_between_steps 76 self._max_steps = context.options.max_steps 77 # Map {id: BreakpointRange} 78 self._leading_bp_handles = {} 79 super(ConditionalController, self).__init__(context, step_collection) 80 self._build_bp_ranges() 81 82 def _build_bp_ranges(self): 83 commands = self.step_collection.commands 84 self._bp_ranges = [] 85 try: 86 limit_commands = commands['DexLimitSteps'] 87 for lc in limit_commands: 88 bpr = BreakpointRange( 89 lc.expression, 90 lc.path, 91 lc.from_line, 92 lc.to_line, 93 lc.values, 94 lc.hit_count, 95 False) 96 self._bp_ranges.append(bpr) 97 except KeyError: 98 raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.') 99 if 'DexFinishTest' in commands: 100 finish_commands = commands['DexFinishTest'] 101 for ic in finish_commands: 102 bpr = BreakpointRange( 103 ic.expression, 104 ic.path, 105 ic.on_line, 106 ic.on_line, 107 ic.values, 108 ic.hit_count + 1, 109 True) 110 self._bp_ranges.append(bpr) 111 112 def _set_leading_bps(self): 113 # Set a leading breakpoint for each BreakpointRange, building a 114 # map of {leading bp id: BreakpointRange}. 115 for bpr in self._bp_ranges: 116 if bpr.has_conditions(): 117 # Add a conditional breakpoint for each condition. 118 for cond_expr in bpr.get_conditional_expression_list(): 119 id = self.debugger.add_conditional_breakpoint(bpr.path, 120 bpr.range_from, 121 cond_expr) 122 self._leading_bp_handles[id] = bpr 123 else: 124 # Add an unconditional breakpoint. 125 id = self.debugger.add_breakpoint(bpr.path, bpr.range_from) 126 self._leading_bp_handles[id] = bpr 127 128 def _run_debugger_custom(self, cmdline): 129 # TODO: Add conditional and unconditional breakpoint support to dbgeng. 130 if self.debugger.get_name() == 'dbgeng': 131 raise DebuggerException('DexLimitSteps commands are not supported by dbgeng') 132 133 self.step_collection.clear_steps() 134 self._set_leading_bps() 135 136 for command_obj in chain.from_iterable(self.step_collection.commands.values()): 137 self._watches.update(command_obj.get_watches()) 138 139 self.debugger.launch(cmdline) 140 time.sleep(self._pause_between_steps) 141 142 exit_desired = False 143 144 while not self.debugger.is_finished: 145 while self.debugger.is_running: 146 pass 147 148 step_info = self.debugger.get_step_info(self._watches, self._step_index) 149 if step_info.current_frame: 150 self._step_index += 1 151 update_step_watches(step_info, self._watches, self.step_collection.commands) 152 self.step_collection.new_step(self.context, step_info) 153 154 bp_to_delete = [] 155 for bp_id in self.debugger.get_triggered_breakpoint_ids(): 156 try: 157 # See if this is one of our leading breakpoints. 158 bpr = self._leading_bp_handles[bp_id] 159 except KeyError: 160 # This is a trailing bp. Mark it for removal. 161 bp_to_delete.append(bp_id) 162 continue 163 164 bpr.add_hit() 165 if bpr.should_be_removed(): 166 if bpr.finish_on_remove: 167 exit_desired = True 168 bp_to_delete.append(bp_id) 169 del self._leading_bp_handles[bp_id] 170 # Add a range of trailing breakpoints covering the lines 171 # requested in the DexLimitSteps command. Ignore first line as 172 # that's covered by the leading bp we just hit and include the 173 # final line. 174 for line in range(bpr.range_from + 1, bpr.range_to + 1): 175 self.debugger.add_breakpoint(bpr.path, line) 176 177 # Remove any trailing or expired leading breakpoints we just hit. 178 self.debugger.delete_breakpoints(bp_to_delete) 179 180 if exit_desired: 181 break 182 self.debugger.go() 183 time.sleep(self._pause_between_steps) 184