Add DebuggerControllerBase and DefaultController to Dexter implements a new architecture that supports new and novel ways of running a debugger under dexter. Current implementation adds the original default behaviour via the new architecture via the DefaultController, this should have NFC. Reviewers: Orlando Differential Revision: https://reviews.llvm.org/D76926
91 lines
3.3 KiB
Python
91 lines
3.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
|
|
"""Base class for controlling debuggers."""
|
|
|
|
from itertools import chain
|
|
import os
|
|
import time
|
|
|
|
from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
|
|
from dex.utils.Exceptions import DebuggerException
|
|
|
|
class DefaultController(DebuggerControllerBase):
|
|
def __init__(self, context, step_collection):
|
|
self.context = context
|
|
self.step_collection = step_collection
|
|
self.watches = set()
|
|
self.step_index = 0
|
|
|
|
def _update_step_watches(self, step_info):
|
|
watch_cmds = ['DexUnreachable', 'DexExpectStepOrder']
|
|
towatch = chain.from_iterable(self.step_collection.commands[x]
|
|
for x in watch_cmds
|
|
if x in self.step_collection.commands)
|
|
try:
|
|
# Iterate over all watches of the types named in watch_cmds
|
|
for watch in towatch:
|
|
loc = step_info.current_location
|
|
if (os.path.exists(loc.path)
|
|
and os.path.samefile(watch.path, loc.path)
|
|
and watch.lineno == loc.lineno):
|
|
result = watch.eval(step_info)
|
|
step_info.watches.update(result)
|
|
break
|
|
except KeyError:
|
|
pass
|
|
|
|
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):
|
|
self.debugger.add_breakpoint(s, line)
|
|
|
|
def _in_source_file(self, step_info):
|
|
if not step_info.current_frame:
|
|
return False
|
|
if not step_info.current_location.path:
|
|
return False
|
|
if not os.path.exists(step_info.current_location.path):
|
|
return False
|
|
return any(os.path.samefile(step_info.current_location.path, f) \
|
|
for f in self.context.options.source_files)
|
|
|
|
def _run_debugger_custom(self):
|
|
self.step_collection.debugger = self.debugger.debugger_info
|
|
self._break_point_all_lines()
|
|
|
|
self.debugger.launch()
|
|
|
|
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
|
|
self.watches.update(command_obj.get_watches())
|
|
|
|
max_steps = self.context.options.max_steps
|
|
for _ in range(max_steps):
|
|
while self.debugger.is_running:
|
|
pass
|
|
|
|
if 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:
|
|
self._update_step_watches(step_info)
|
|
self.step_collection.new_step(self.context, step_info)
|
|
|
|
if self._in_source_file(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))
|