diff options
Diffstat (limited to 'cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py')
| -rw-r--r-- | cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py | 238 |
1 files changed, 200 insertions, 38 deletions
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py index c53f1419ee13..f84c4fd153c7 100644 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py @@ -22,7 +22,7 @@ from dex.debugger.DebuggerControllers.DebuggerControllerBase import ( from dex.debugger.DebuggerBase import DebuggerBase from dex.utils.Exceptions import DebuggerException from dex.utils.Timeout import Timeout - +from dex.dextIR import LocIR class BreakpointRange: """A range of breakpoints and a set of conditions. @@ -51,6 +51,9 @@ class BreakpointRange: values: list, hit_count: int, finish_on_remove: bool, + is_continue: bool = False, + function: str = None, + addr: str = None, ): self.expression = expression self.path = path @@ -60,6 +63,72 @@ class BreakpointRange: self.max_hit_count = hit_count self.current_hit_count = 0 self.finish_on_remove = finish_on_remove + self.is_continue = is_continue + self.function = function + self.addr = addr + + def limit_steps( + expression: str, + path: str, + range_from: int, + range_to: int, + values: list, + hit_count: int, + ): + return BreakpointRange( + expression, + path, + range_from, + range_to, + values, + hit_count, + False, + ) + + def finish_test( + expression: str, path: str, on_line: int, values: list, hit_count: int + ): + return BreakpointRange( + expression, + path, + on_line, + on_line, + values, + hit_count, + True, + ) + + def continue_from_to( + expression: str, + path: str, + from_line: int, + to_line: int, + values: list, + hit_count: int, + ): + return BreakpointRange( + expression, + path, + from_line, + to_line, + values, + hit_count, + finish_on_remove=False, + is_continue=True, + ) + + def step_function(function: str, path: str, hit_count: int): + return BreakpointRange( + None, + path, + None, + None, + None, + hit_count, + finish_on_remove=False, + is_continue=False, + function=function, + ) def has_conditions(self): return self.expression is not None @@ -96,34 +165,40 @@ class ConditionalController(DebuggerControllerBase): def _build_bp_ranges(self): commands = self.step_collection.commands self._bp_ranges = [] - try: - limit_commands = commands["DexLimitSteps"] - for lc in limit_commands: - bpr = BreakpointRange( - lc.expression, - lc.path, - lc.from_line, - lc.to_line, - lc.values, - lc.hit_count, - False, - ) - self._bp_ranges.append(bpr) - except KeyError: + + cond_controller_cmds = ["DexLimitSteps", "DexStepFunction", "DexContinue"] + if not any(c in commands for c in cond_controller_cmds): raise DebuggerException( - "Missing DexLimitSteps commands, cannot conditionally step." + f"No conditional commands {cond_controller_cmds}, cannot conditionally step." ) + + if "DexLimitSteps" in commands: + for c in commands["DexLimitSteps"]: + bpr = BreakpointRange.limit_steps( + c.expression, + c.path, + c.from_line, + c.to_line, + c.values, + c.hit_count, + ) + self._bp_ranges.append(bpr) if "DexFinishTest" in commands: - finish_commands = commands["DexFinishTest"] - for ic in finish_commands: - bpr = BreakpointRange( - ic.expression, - ic.path, - ic.on_line, - ic.on_line, - ic.values, - ic.hit_count + 1, - True, + for c in commands["DexFinishTest"]: + bpr = BreakpointRange.finish_test( + c.expression, c.path, c.on_line, c.values, c.hit_count + 1 + ) + self._bp_ranges.append(bpr) + if "DexContinue" in commands: + for c in commands["DexContinue"]: + bpr = BreakpointRange.continue_from_to( + c.expression, c.path, c.from_line, c.to_line, c.values, c.hit_count + ) + self._bp_ranges.append(bpr) + if "DexStepFunction" in commands: + for c in commands["DexStepFunction"]: + bpr = BreakpointRange.step_function( + c.get_function(), c.path, c.hit_count ) self._bp_ranges.append(bpr) @@ -138,6 +213,9 @@ class ConditionalController(DebuggerControllerBase): bpr.path, bpr.range_from, cond_expr ) self._leading_bp_handles[id] = bpr + elif bpr.function is not None: + id = self.debugger.add_function_breakpoint(bpr.function) + self._leading_bp_handles[id] = bpr else: # Add an unconditional breakpoint. id = self.debugger.add_breakpoint(bpr.path, bpr.range_from) @@ -163,6 +241,9 @@ class ConditionalController(DebuggerControllerBase): timed_out = False total_timeout = Timeout(self.context.options.timeout_total) + step_function_backtraces: list[list[str]] = [] + self.instr_bp_ids = set() + while not self.debugger.is_finished: breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) while self.debugger.is_running and not timed_out: @@ -185,21 +266,25 @@ class ConditionalController(DebuggerControllerBase): break step_info = self.debugger.get_step_info(self._watches, self._step_index) + backtrace = None if step_info.current_frame: - self._step_index += 1 - update_step_watches( - step_info, self._watches, self.step_collection.commands - ) - self.step_collection.new_step(self.context, step_info) + backtrace = [f.function for f in step_info.frames] + record_step = False + debugger_continue = False bp_to_delete = [] for bp_id in self.debugger.get_triggered_breakpoint_ids(): try: # See if this is one of our leading breakpoints. bpr = self._leading_bp_handles[bp_id] + record_step = True except KeyError: # This is a trailing bp. Mark it for removal. bp_to_delete.append(bp_id) + if bp_id in self.instr_bp_ids: + self.instr_bp_ids.remove(bp_id) + else: + record_step = True continue bpr.add_hit() @@ -208,17 +293,94 @@ class ConditionalController(DebuggerControllerBase): exit_desired = True bp_to_delete.append(bp_id) del self._leading_bp_handles[bp_id] - # Add a range of trailing breakpoints covering the lines - # requested in the DexLimitSteps command. Ignore first line as - # that's covered by the leading bp we just hit and include the - # final line. - for line in range(bpr.range_from + 1, bpr.range_to + 1): - self.debugger.add_breakpoint(bpr.path, line) + + if bpr.function is not None: + if step_info.frames: + # Add this backtrace to the stack. While the current + # backtrace matches the top of the stack we'll step, + # and while there's a backtrace in the stack that + # is a subset of the current backtrace we'll step-out. + if ( + len(step_function_backtraces) == 0 + or backtrace != step_function_backtraces[-1] + ): + step_function_backtraces.append(backtrace) + + # Add an address breakpoint so we don't fall out + # the end of nested DexStepFunctions with a DexContinue. + addr = self.debugger.get_pc(frame_idx=1) + instr_id = self.debugger.add_instruction_breakpoint(addr) + # Note the breakpoint so we don't log the source location + # it in the trace later. + self.instr_bp_ids.add(instr_id) + + elif bpr.is_continue: + debugger_continue = True + if bpr.range_to is not None: + self.debugger.add_breakpoint(bpr.path, bpr.range_to) + + else: + # Add a range of trailing breakpoints covering the lines + # requested in the DexLimitSteps command. Ignore first line as + # that's covered by the leading bp we just hit and include the + # final line. + for line in range(bpr.range_from + 1, bpr.range_to + 1): + id = self.debugger.add_breakpoint(bpr.path, line) # Remove any trailing or expired leading breakpoints we just hit. self.debugger.delete_breakpoints(bp_to_delete) + debugger_next = False + debugger_out = False + if not debugger_continue and step_info.current_frame and step_info.frames: + while len(step_function_backtraces) > 0: + match_subtrace = False # Backtrace contains a target trace. + match_trace = False # Backtrace matches top of target stack. + + # The top of the step_function_backtraces stack contains a + # backtrace that we want to step through. Check if the + # current backtrace ("backtrace") either matches that trace + # or otherwise contains it. + target_backtrace = step_function_backtraces[-1] + if len(backtrace) >= len(target_backtrace): + match_trace = len(backtrace) == len(target_backtrace) + # Check if backtrace contains target_backtrace, matching + # from the end (bottom of call stack) backwards. + match_subtrace = ( + backtrace[-len(target_backtrace) :] == target_backtrace + ) + + if match_trace: + # We want to step through this function; do so and + # log the steps in the step trace. + debugger_next = True + record_step = True + break + elif match_subtrace: + # There's a function we care about buried in the + # current backtrace. Step-out until we get to it. + debugger_out = True + break + else: + # Drop backtraces that are not match_subtraces of the current + # backtrace; the functions we wanted to step through + # there are no longer reachable. + step_function_backtraces.pop() + + if record_step and step_info.current_frame: + self._step_index += 1 + # Record the step. + update_step_watches( + step_info, self._watches, self.step_collection.commands + ) + self.step_collection.new_step(self.context, step_info) + if exit_desired: break - self.debugger.go() + elif debugger_next: + self.debugger.step_next() + elif debugger_out: + self.debugger.step_out() + else: + self.debugger.go() time.sleep(self._pause_between_steps) |
