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 ConditionalBpRange: 22 """Represents a conditional range of breakpoints within a source file descending from 23 one line to another.""" 24 25 def __init__(self, expression: str, path: str, range_from: int, range_to: int, values: list): 26 self.expression = expression 27 self.path = path 28 self.range_from = range_from 29 self.range_to = range_to 30 self.conditional_values = values 31 32 def get_conditional_expression_list(self): 33 conditional_list = [] 34 for value in self.conditional_values: 35 # (<expression>) == (<value>) 36 conditional_expression = '({}) == ({})'.format(self.expression, value) 37 conditional_list.append(conditional_expression) 38 return conditional_list 39 40 41class ConditionalController(DebuggerControllerBase): 42 def __init__(self, context, step_collection): 43 self.context = context 44 self.step_collection = step_collection 45 self._conditional_bps = None 46 self._watches = set() 47 self._step_index = 0 48 self._build_conditional_bps() 49 self._path_and_line_to_conditional_bp = defaultdict(list) 50 self._pause_between_steps = context.options.pause_between_steps 51 self._max_steps = context.options.max_steps 52 53 def _build_conditional_bps(self): 54 commands = self.step_collection.commands 55 self._conditional_bps = [] 56 try: 57 limit_commands = commands['DexLimitSteps'] 58 for lc in limit_commands: 59 conditional_bp = ConditionalBpRange( 60 lc.expression, 61 lc.path, 62 lc.from_line, 63 lc.to_line, 64 lc.values) 65 self._conditional_bps.append(conditional_bp) 66 except KeyError: 67 raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.') 68 69 def _set_conditional_bps(self): 70 # When we break in the debugger we need a quick and easy way to look up 71 # which conditional bp we've breaked on. 72 for cbp in self._conditional_bps: 73 conditional_bp_list = self._path_and_line_to_conditional_bp[(cbp.path, cbp.range_from)] 74 conditional_bp_list.append(cbp) 75 76 # Set break points only on the first line of any conditional range, we'll set 77 # more break points for a range when the condition is satisfied. 78 for cbp in self._conditional_bps: 79 for cond_expr in cbp.get_conditional_expression_list(): 80 self.debugger.add_conditional_breakpoint(cbp.path, cbp.range_from, cond_expr) 81 82 def _conditional_met(self, cbp): 83 for cond_expr in cbp.get_conditional_expression_list(): 84 valueIR = self.debugger.evaluate_expression(cond_expr) 85 if valueIR.type_name == 'bool' and valueIR.value == 'true': 86 return True 87 return False 88 89 def _run_debugger_custom(self): 90 # TODO: Add conditional and unconditional breakpoint support to dbgeng. 91 if self.debugger.get_name() == 'dbgeng': 92 raise DebuggerException('DexLimitSteps commands are not supported by dbgeng') 93 94 self.step_collection.clear_steps() 95 self._set_conditional_bps() 96 97 for command_obj in chain.from_iterable(self.step_collection.commands.values()): 98 self._watches.update(command_obj.get_watches()) 99 100 self.debugger.launch() 101 time.sleep(self._pause_between_steps) 102 while not self.debugger.is_finished: 103 while self.debugger.is_running: 104 pass 105 106 step_info = self.debugger.get_step_info(self._watches, self._step_index) 107 if step_info.current_frame: 108 self._step_index += 1 109 update_step_watches(step_info, self._watches, self.step_collection.commands) 110 self.step_collection.new_step(self.context, step_info) 111 112 loc = step_info.current_location 113 conditional_bp_key = (loc.path, loc.lineno) 114 if conditional_bp_key in self._path_and_line_to_conditional_bp: 115 116 conditional_bps = self._path_and_line_to_conditional_bp[conditional_bp_key] 117 for cbp in conditional_bps: 118 if self._conditional_met(cbp): 119 # Unconditional range should ignore first line as that's the 120 # conditional bp we just hit and should be inclusive of final line 121 for line in range(cbp.range_from + 1, cbp.range_to + 1): 122 self.debugger.add_conditional_breakpoint(cbp.path, line, condition='') 123 124 # Clear any uncondtional break points at this loc. 125 self.debugger.delete_conditional_breakpoint(file_=loc.path, line=loc.lineno, condition='') 126 self.debugger.go() 127 time.sleep(self._pause_between_steps) 128