11364750dSJames Henderson# DExTer : Debugging Experience Tester 21364750dSJames Henderson# ~~~~~~ ~ ~~ ~ ~~ 31364750dSJames Henderson# 41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information. 61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 71364750dSJames Henderson"""Conditional Controller Class for DExTer.-""" 81364750dSJames Henderson 91364750dSJames Henderson 101364750dSJames Hendersonimport os 111364750dSJames Hendersonimport time 121364750dSJames Hendersonfrom collections import defaultdict 131364750dSJames Hendersonfrom itertools import chain 141364750dSJames Henderson 151364750dSJames Hendersonfrom dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches 161364750dSJames Hendersonfrom dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase 171364750dSJames Hendersonfrom dex.debugger.DebuggerBase import DebuggerBase 181364750dSJames Hendersonfrom dex.utils.Exceptions import DebuggerException 191364750dSJames Henderson 201364750dSJames Henderson 211364750dSJames Hendersonclass BreakpointRange: 221364750dSJames Henderson """A range of breakpoints and a set of conditions. 231364750dSJames Henderson 241364750dSJames Henderson The leading breakpoint (on line `range_from`) is always active. 251364750dSJames Henderson 261364750dSJames Henderson When the leading breakpoint is hit the trailing range should be activated 271364750dSJames Henderson when `expression` evaluates to any value in `values`. If there are no 281364750dSJames Henderson conditions (`expression` is None) then the trailing breakpoint range should 291364750dSJames Henderson always be activated upon hitting the leading breakpoint. 301364750dSJames Henderson 311364750dSJames Henderson Args: 321364750dSJames Henderson expression: None for no conditions, or a str expression to compare 331364750dSJames Henderson against `values`. 341364750dSJames Henderson 351364750dSJames Henderson hit_count: None for no limit, or int to set the number of times the 361364750dSJames Henderson leading breakpoint is triggered before it is removed. 371364750dSJames Henderson """ 381364750dSJames Henderson 391364750dSJames Henderson def __init__(self, expression: str, path: str, range_from: int, range_to: int, 406cf69179SStephen Tozer values: list, hit_count: int, finish_on_remove: bool): 411364750dSJames Henderson self.expression = expression 421364750dSJames Henderson self.path = path 431364750dSJames Henderson self.range_from = range_from 441364750dSJames Henderson self.range_to = range_to 451364750dSJames Henderson self.conditional_values = values 461364750dSJames Henderson self.max_hit_count = hit_count 471364750dSJames Henderson self.current_hit_count = 0 486cf69179SStephen Tozer self.finish_on_remove = finish_on_remove 491364750dSJames Henderson 501364750dSJames Henderson def has_conditions(self): 511364750dSJames Henderson return self.expression != None 521364750dSJames Henderson 531364750dSJames Henderson def get_conditional_expression_list(self): 541364750dSJames Henderson conditional_list = [] 551364750dSJames Henderson for value in self.conditional_values: 561364750dSJames Henderson # (<expression>) == (<value>) 571364750dSJames Henderson conditional_expression = '({}) == ({})'.format(self.expression, value) 581364750dSJames Henderson conditional_list.append(conditional_expression) 591364750dSJames Henderson return conditional_list 601364750dSJames Henderson 611364750dSJames Henderson def add_hit(self): 621364750dSJames Henderson self.current_hit_count += 1 631364750dSJames Henderson 641364750dSJames Henderson def should_be_removed(self): 651364750dSJames Henderson if self.max_hit_count == None: 661364750dSJames Henderson return False 671364750dSJames Henderson return self.current_hit_count >= self.max_hit_count 681364750dSJames Henderson 691364750dSJames Henderson 701364750dSJames Hendersonclass ConditionalController(DebuggerControllerBase): 711364750dSJames Henderson def __init__(self, context, step_collection): 721364750dSJames Henderson self._bp_ranges = None 731364750dSJames Henderson self._watches = set() 741364750dSJames Henderson self._step_index = 0 751364750dSJames Henderson self._pause_between_steps = context.options.pause_between_steps 761364750dSJames Henderson self._max_steps = context.options.max_steps 771364750dSJames Henderson # Map {id: BreakpointRange} 781364750dSJames Henderson self._leading_bp_handles = {} 793a094d8bSJeremy Morse super(ConditionalController, self).__init__(context, step_collection) 803a094d8bSJeremy Morse self._build_bp_ranges() 811364750dSJames Henderson 821364750dSJames Henderson def _build_bp_ranges(self): 831364750dSJames Henderson commands = self.step_collection.commands 841364750dSJames Henderson self._bp_ranges = [] 851364750dSJames Henderson try: 861364750dSJames Henderson limit_commands = commands['DexLimitSteps'] 871364750dSJames Henderson for lc in limit_commands: 881364750dSJames Henderson bpr = BreakpointRange( 891364750dSJames Henderson lc.expression, 901364750dSJames Henderson lc.path, 911364750dSJames Henderson lc.from_line, 921364750dSJames Henderson lc.to_line, 931364750dSJames Henderson lc.values, 946cf69179SStephen Tozer lc.hit_count, 956cf69179SStephen Tozer False) 961364750dSJames Henderson self._bp_ranges.append(bpr) 971364750dSJames Henderson except KeyError: 981364750dSJames Henderson raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.') 996cf69179SStephen Tozer if 'DexFinishTest' in commands: 1006cf69179SStephen Tozer finish_commands = commands['DexFinishTest'] 1016cf69179SStephen Tozer for ic in finish_commands: 1026cf69179SStephen Tozer bpr = BreakpointRange( 1036cf69179SStephen Tozer ic.expression, 1046cf69179SStephen Tozer ic.path, 1056cf69179SStephen Tozer ic.on_line, 1066cf69179SStephen Tozer ic.on_line, 1076cf69179SStephen Tozer ic.values, 1086cf69179SStephen Tozer ic.hit_count + 1, 1096cf69179SStephen Tozer True) 1106cf69179SStephen Tozer self._bp_ranges.append(bpr) 1111364750dSJames Henderson 1121364750dSJames Henderson def _set_leading_bps(self): 1131364750dSJames Henderson # Set a leading breakpoint for each BreakpointRange, building a 1141364750dSJames Henderson # map of {leading bp id: BreakpointRange}. 1151364750dSJames Henderson for bpr in self._bp_ranges: 1161364750dSJames Henderson if bpr.has_conditions(): 1171364750dSJames Henderson # Add a conditional breakpoint for each condition. 1181364750dSJames Henderson for cond_expr in bpr.get_conditional_expression_list(): 1191364750dSJames Henderson id = self.debugger.add_conditional_breakpoint(bpr.path, 1201364750dSJames Henderson bpr.range_from, 1211364750dSJames Henderson cond_expr) 1221364750dSJames Henderson self._leading_bp_handles[id] = bpr 1231364750dSJames Henderson else: 1241364750dSJames Henderson # Add an unconditional breakpoint. 1251364750dSJames Henderson id = self.debugger.add_breakpoint(bpr.path, bpr.range_from) 1261364750dSJames Henderson self._leading_bp_handles[id] = bpr 1271364750dSJames Henderson 1283a094d8bSJeremy Morse def _run_debugger_custom(self, cmdline): 1291364750dSJames Henderson # TODO: Add conditional and unconditional breakpoint support to dbgeng. 1301364750dSJames Henderson if self.debugger.get_name() == 'dbgeng': 1311364750dSJames Henderson raise DebuggerException('DexLimitSteps commands are not supported by dbgeng') 1321364750dSJames Henderson 1331364750dSJames Henderson self.step_collection.clear_steps() 1341364750dSJames Henderson self._set_leading_bps() 1351364750dSJames Henderson 1361364750dSJames Henderson for command_obj in chain.from_iterable(self.step_collection.commands.values()): 1371364750dSJames Henderson self._watches.update(command_obj.get_watches()) 1381364750dSJames Henderson 1393a094d8bSJeremy Morse self.debugger.launch(cmdline) 1401364750dSJames Henderson time.sleep(self._pause_between_steps) 1416cf69179SStephen Tozer 1426cf69179SStephen Tozer exit_desired = False 1436cf69179SStephen Tozer 1441364750dSJames Henderson while not self.debugger.is_finished: 1451364750dSJames Henderson while self.debugger.is_running: 1461364750dSJames Henderson pass 1471364750dSJames Henderson 1481364750dSJames Henderson step_info = self.debugger.get_step_info(self._watches, self._step_index) 1491364750dSJames Henderson if step_info.current_frame: 1501364750dSJames Henderson self._step_index += 1 1511364750dSJames Henderson update_step_watches(step_info, self._watches, self.step_collection.commands) 1521364750dSJames Henderson self.step_collection.new_step(self.context, step_info) 1531364750dSJames Henderson 1541364750dSJames Henderson bp_to_delete = [] 1551364750dSJames Henderson for bp_id in self.debugger.get_triggered_breakpoint_ids(): 1561364750dSJames Henderson try: 1571364750dSJames Henderson # See if this is one of our leading breakpoints. 1581364750dSJames Henderson bpr = self._leading_bp_handles[bp_id] 1591364750dSJames Henderson except KeyError: 1601364750dSJames Henderson # This is a trailing bp. Mark it for removal. 1611364750dSJames Henderson bp_to_delete.append(bp_id) 1621364750dSJames Henderson continue 1631364750dSJames Henderson 1641364750dSJames Henderson bpr.add_hit() 1651364750dSJames Henderson if bpr.should_be_removed(): 1666cf69179SStephen Tozer if bpr.finish_on_remove: 1676cf69179SStephen Tozer exit_desired = True 1681364750dSJames Henderson bp_to_delete.append(bp_id) 1691364750dSJames Henderson del self._leading_bp_handles[bp_id] 1701364750dSJames Henderson # Add a range of trailing breakpoints covering the lines 1711364750dSJames Henderson # requested in the DexLimitSteps command. Ignore first line as 1721364750dSJames Henderson # that's covered by the leading bp we just hit and include the 1731364750dSJames Henderson # final line. 1741364750dSJames Henderson for line in range(bpr.range_from + 1, bpr.range_to + 1): 1751364750dSJames Henderson self.debugger.add_breakpoint(bpr.path, line) 1761364750dSJames Henderson 1771364750dSJames Henderson # Remove any trailing or expired leading breakpoints we just hit. 178*b3f14802Sgbtozers self.debugger.delete_breakpoints(bp_to_delete) 1791364750dSJames Henderson 1806cf69179SStephen Tozer if exit_desired: 1816cf69179SStephen Tozer break 1821364750dSJames Henderson self.debugger.go() 1831364750dSJames Henderson time.sleep(self._pause_between_steps) 184