blob: e225a48bcb66a6acfe8df1a5f058c0842d9903c6 [file] [log] [blame]
# 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 ConditionalBpRange:
"""Represents a conditional range of breakpoints within a source file descending from
one line to another."""
def __init__(self, expression: str, path: str, range_from: int, range_to: int, values: list):
self.expression = expression
self.path = path
self.range_from = range_from
self.range_to = range_to
self.conditional_values = values
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
class ConditionalController(DebuggerControllerBase):
def __init__(self, context, step_collection):
self.context = context
self.step_collection = step_collection
self._conditional_bp_ranges = None
self._build_conditional_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: ConditionalBpRange}
self._conditional_bp_handles = {}
def _build_conditional_bp_ranges(self):
commands = self.step_collection.commands
self._conditional_bp_ranges = []
try:
limit_commands = commands['DexLimitSteps']
for lc in limit_commands:
conditional_bp = ConditionalBpRange(
lc.expression,
lc.path,
lc.from_line,
lc.to_line,
lc.values)
self._conditional_bp_ranges.append(conditional_bp)
except KeyError:
raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
def _set_conditional_bps(self):
# Set a conditional breakpoint for each ConditionalBpRange and build a
# map of {id: ConditionalBpRange}.
for cbp in self._conditional_bp_ranges:
for cond_expr in cbp.get_conditional_expression_list():
id = self.debugger.add_conditional_breakpoint(cbp.path,
cbp.range_from,
cond_expr)
self._conditional_bp_handles[id] = cbp
def _conditional_met(self, cbp):
for cond_expr in cbp.get_conditional_expression_list():
valueIR = self.debugger.evaluate_expression(cond_expr)
if valueIR.type_name == 'bool' and valueIR.value == 'true':
return True
return False
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_conditional_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 conditional breakpoints.
cbp = self._conditional_bp_handles[bp_id]
except KeyError:
# This is an unconditional bp. Mark it for removal.
bp_to_delete.append(bp_id)
continue
# We have triggered a breakpoint with a condition. Check that
# the condition has been met.
if self._conditional_met(cbp):
# Add a range of unconditional breakpoints covering the
# lines requested in the DexLimitSteps command. Ignore
# first line as that's the conditional bp we just hit and
# include the final line.
for line in range(cbp.range_from + 1, cbp.range_to + 1):
self.debugger.add_breakpoint(cbp.path, line)
# Remove any unconditional 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)