
Discussion thread: https://lists.llvm.org/pipermail/llvm-dev/2021-January/148048.html Move debuginfo-test into a subdirectory of a new top-level directory, called cross-project-tests. The new name replaces "debuginfo-test" as an LLVM project enabled via LLVM_ENABLE_PROJECTS. Differential Revision: https://reviews.llvm.org/D95339 Reviewed by: aprantl
165 lines
6.5 KiB
Python
165 lines
6.5 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
|
|
"""Conditional Controller Class for DExTer.-"""
|
|
|
|
|
|
import os
|
|
import time
|
|
from collections import defaultdict
|
|
from itertools import chain
|
|
|
|
from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
|
|
from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
|
|
from dex.debugger.DebuggerBase import DebuggerBase
|
|
from dex.utils.Exceptions import DebuggerException
|
|
|
|
|
|
class BreakpointRange:
|
|
"""A range of breakpoints and a set of conditions.
|
|
|
|
The leading breakpoint (on line `range_from`) is always active.
|
|
|
|
When the leading breakpoint is hit the trailing range should be activated
|
|
when `expression` evaluates to any value in `values`. If there are no
|
|
conditions (`expression` is None) then the trailing breakpoint range should
|
|
always be activated upon hitting the leading breakpoint.
|
|
|
|
Args:
|
|
expression: None for no conditions, or a str expression to compare
|
|
against `values`.
|
|
|
|
hit_count: None for no limit, or int to set the number of times the
|
|
leading breakpoint is triggered before it is removed.
|
|
"""
|
|
|
|
def __init__(self, expression: str, path: str, range_from: int, range_to: int,
|
|
values: list, hit_count: int):
|
|
self.expression = expression
|
|
self.path = path
|
|
self.range_from = range_from
|
|
self.range_to = range_to
|
|
self.conditional_values = values
|
|
self.max_hit_count = hit_count
|
|
self.current_hit_count = 0
|
|
|
|
def has_conditions(self):
|
|
return self.expression != None
|
|
|
|
def get_conditional_expression_list(self):
|
|
conditional_list = []
|
|
for value in self.conditional_values:
|
|
# (<expression>) == (<value>)
|
|
conditional_expression = '({}) == ({})'.format(self.expression, value)
|
|
conditional_list.append(conditional_expression)
|
|
return conditional_list
|
|
|
|
def add_hit(self):
|
|
self.current_hit_count += 1
|
|
|
|
def should_be_removed(self):
|
|
if self.max_hit_count == None:
|
|
return False
|
|
return self.current_hit_count >= self.max_hit_count
|
|
|
|
|
|
class ConditionalController(DebuggerControllerBase):
|
|
def __init__(self, context, step_collection):
|
|
self.context = context
|
|
self.step_collection = step_collection
|
|
self._bp_ranges = None
|
|
self._build_bp_ranges()
|
|
self._watches = set()
|
|
self._step_index = 0
|
|
self._pause_between_steps = context.options.pause_between_steps
|
|
self._max_steps = context.options.max_steps
|
|
# Map {id: BreakpointRange}
|
|
self._leading_bp_handles = {}
|
|
|
|
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)
|
|
self._bp_ranges.append(bpr)
|
|
except KeyError:
|
|
raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
|
|
|
|
def _set_leading_bps(self):
|
|
# Set a leading breakpoint for each BreakpointRange, building a
|
|
# map of {leading bp id: BreakpointRange}.
|
|
for bpr in self._bp_ranges:
|
|
if bpr.has_conditions():
|
|
# Add a conditional breakpoint for each condition.
|
|
for cond_expr in bpr.get_conditional_expression_list():
|
|
id = self.debugger.add_conditional_breakpoint(bpr.path,
|
|
bpr.range_from,
|
|
cond_expr)
|
|
self._leading_bp_handles[id] = bpr
|
|
else:
|
|
# Add an unconditional breakpoint.
|
|
id = self.debugger.add_breakpoint(bpr.path, bpr.range_from)
|
|
self._leading_bp_handles[id] = bpr
|
|
|
|
def _run_debugger_custom(self):
|
|
# TODO: Add conditional and unconditional breakpoint support to dbgeng.
|
|
if self.debugger.get_name() == 'dbgeng':
|
|
raise DebuggerException('DexLimitSteps commands are not supported by dbgeng')
|
|
|
|
self.step_collection.clear_steps()
|
|
self._set_leading_bps()
|
|
|
|
for command_obj in chain.from_iterable(self.step_collection.commands.values()):
|
|
self._watches.update(command_obj.get_watches())
|
|
|
|
self.debugger.launch()
|
|
time.sleep(self._pause_between_steps)
|
|
while not self.debugger.is_finished:
|
|
while self.debugger.is_running:
|
|
pass
|
|
|
|
step_info = self.debugger.get_step_info(self._watches, self._step_index)
|
|
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)
|
|
|
|
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]
|
|
except KeyError:
|
|
# This is a trailing bp. Mark it for removal.
|
|
bp_to_delete.append(bp_id)
|
|
continue
|
|
|
|
bpr.add_hit()
|
|
if bpr.should_be_removed():
|
|
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)
|
|
|
|
# Remove any trailing or expired leading breakpoints we just hit.
|
|
for bp_id in bp_to_delete:
|
|
self.debugger.delete_breakpoint(bp_id)
|
|
|
|
self.debugger.go()
|
|
time.sleep(self._pause_between_steps)
|