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 --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py @@ -16,6 +16,7 @@ from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase from dex.debugger.DebuggerBase import DebuggerBase from dex.utils.Exceptions import DebuggerException +from dex.utils.Timeout import Timeout class BreakpointRange: @@ -140,10 +141,26 @@ time.sleep(self._pause_between_steps) exit_desired = False + timed_out = False + total_timeout = Timeout(self.context.options.timeout_total) while not self.debugger.is_finished: - while self.debugger.is_running: - pass + + breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) + while self.debugger.is_running and not timed_out: + # Check to see whether we've timed out while we're waiting. + if total_timeout.timed_out(): + self.context.logger.error('Debugger session has been ' + f'running for {total_timeout.elapsed}s, timeout reached!') + timed_out = True + if breakpoint_timeout.timed_out(): + self.context.logger.error(f'Debugger session has not ' + f'hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout ' + 'reached!') + timed_out = True + + if timed_out: + break step_info = self.debugger.get_step_info(self._watches, self._step_index) if step_info.current_frame: diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py @@ -13,6 +13,7 @@ from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches from dex.utils.Exceptions import DebuggerException, LoadDebuggerException +from dex.utils.Timeout import Timeout class EarlyExitCondition(object): def __init__(self, on_line, hit_count, expression, values): @@ -81,12 +82,25 @@ self.watches.update(command_obj.get_watches()) early_exit_conditions = self._get_early_exit_conditions() + timed_out = False + total_timeout = Timeout(self.context.options.timeout_total) max_steps = self.context.options.max_steps for _ in range(max_steps): - while self.debugger.is_running: - pass - if self.debugger.is_finished: + breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint) + while self.debugger.is_running and not timed_out: + # Check to see whether we've timed out while we're waiting. + if total_timeout.timed_out(): + self.context.logger.error('Debugger session has been ' + f'running for {total_timeout.elapsed}s, timeout reached!') + timed_out = True + if breakpoint_timeout.timed_out(): + self.context.logger.error(f'Debugger session has not ' + f'hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout ' + 'reached!') + timed_out = True + + if timed_out or self.debugger.is_finished: break self.step_index += 1 diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/Debuggers.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/Debuggers.py --- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/Debuggers.py +++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/Debuggers.py @@ -121,7 +121,21 @@ default='', help='command line arguments for the test program, in addition to any ' 'provided by DexCommandLine') - + parser.add_argument( + '--timeout-total', + metavar='', + type=float, + default=0.0, + help='if >0, debugger session will automatically exit after ' + 'running for seconds') + parser.add_argument( + '--timeout-breakpoint', + metavar='', + type=float, + default=0.0, + help='if >0, debugger session will automatically exit after ' + 'waiting seconds without hitting a ' + 'breakpoint') def handle_debugger_tool_base_options(context, defaults): # noqa options = context.options diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/utils/Timeout.py b/cross-project-tests/debuginfo-tests/dexter/dex/utils/Timeout.py new file mode 100644 --- /dev/null +++ b/cross-project-tests/debuginfo-tests/dexter/dex/utils/Timeout.py @@ -0,0 +1,31 @@ +# DExTer : Debugging Experience Tester +# ~~~~~~ ~ ~~ ~ ~~ +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""Utility class to check for timeouts. Timer starts when the object is initialized, +and can be checked by calling timed_out(). Passing a timeout value of 0.0 or less +means a timeout will never be triggered, i.e. timed_out() will always return False. +""" + +import time + +class Timeout(object): + + def __init__(self, duration: float): + self.start = self.now + self.duration = duration + + def timed_out(self): + if self.duration <= 0.0: + return False + return self.elapsed > self.duration + + @property + def elapsed(self): + return self.now - self.start + + @property + def now(self): + return time.time()