Adds a pair of options for Dexter that allow the user to specify a timeout duration. These options are: * --timeout-total: Times out if the total run-time of the debugger session exceeds <timeout-total> seconds. * --timeout-breakpoint: Times out if the time without hitting a breakpoint exceeds <timeout-breakpoint> seconds. Reviewed By: Orlando Differential Revision: https://reviews.llvm.org/D145063
124 lines
5.3 KiB
Python
124 lines
5.3 KiB
Python
# 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
|
|
"""Default class for controlling debuggers."""
|
|
|
|
from itertools import chain
|
|
import os
|
|
import time
|
|
|
|
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):
|
|
self.on_line = on_line
|
|
self.hit_count = hit_count
|
|
self.expression = expression
|
|
self.values = values
|
|
|
|
class DefaultController(DebuggerControllerBase):
|
|
def __init__(self, context, step_collection):
|
|
self.source_files = context.options.source_files
|
|
self.watches = set()
|
|
self.step_index = 0
|
|
super(DefaultController, self).__init__(context, step_collection)
|
|
|
|
def _break_point_all_lines(self):
|
|
for s in self.context.options.source_files:
|
|
with open(s, 'r') as fp:
|
|
num_lines = len(fp.readlines())
|
|
for line in range(1, num_lines + 1):
|
|
try:
|
|
self.debugger.add_breakpoint(s, line)
|
|
except DebuggerException:
|
|
raise LoadDebuggerException(DebuggerException.msg)
|
|
|
|
def _get_early_exit_conditions(self):
|
|
commands = self.step_collection.commands
|
|
early_exit_conditions = []
|
|
if 'DexFinishTest' in commands:
|
|
finish_commands = commands['DexFinishTest']
|
|
for fc in finish_commands:
|
|
condition = EarlyExitCondition(on_line=fc.on_line,
|
|
hit_count=fc.hit_count,
|
|
expression=fc.expression,
|
|
values=fc.values)
|
|
early_exit_conditions.append(condition)
|
|
return early_exit_conditions
|
|
|
|
def _should_exit(self, early_exit_conditions, line_no):
|
|
for condition in early_exit_conditions:
|
|
if condition.on_line == line_no:
|
|
exit_condition_hit = condition.expression is None
|
|
if condition.expression is not None:
|
|
# For the purposes of consistent behaviour with the
|
|
# Conditional Controller, check equality in the debugger
|
|
# rather than in python (as the two can differ).
|
|
for value in condition.values:
|
|
expr_val = self.debugger.evaluate_expression(f'({condition.expression}) == ({value})')
|
|
if expr_val.value == 'true':
|
|
exit_condition_hit = True
|
|
break
|
|
if exit_condition_hit:
|
|
if condition.hit_count <= 0:
|
|
return True
|
|
else:
|
|
condition.hit_count -= 1
|
|
return False
|
|
|
|
|
|
def _run_debugger_custom(self, cmdline):
|
|
self.step_collection.debugger = self.debugger.debugger_info
|
|
self._break_point_all_lines()
|
|
self.debugger.launch(cmdline)
|
|
|
|
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
|
|
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):
|
|
|
|
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
|
|
step_info = self.debugger.get_step_info(self.watches, self.step_index)
|
|
|
|
if step_info.current_frame:
|
|
update_step_watches(step_info, self.watches, self.step_collection.commands)
|
|
self.step_collection.new_step(self.context, step_info)
|
|
if self._should_exit(early_exit_conditions, step_info.current_frame.loc.lineno):
|
|
break
|
|
|
|
if in_source_file(self.source_files, step_info):
|
|
self.debugger.step()
|
|
else:
|
|
self.debugger.go()
|
|
|
|
time.sleep(self.context.options.pause_between_steps)
|
|
else:
|
|
raise DebuggerException(
|
|
'maximum number of steps reached ({})'.format(max_steps))
|