• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# DExTer : Debugging Experience Tester
2# ~~~~~~   ~         ~~         ~   ~~
3#
4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5# See https://llvm.org/LICENSE.txt for license information.
6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7"""Conditional Controller Class for DExTer.-"""
8
9
10import os
11import time
12from collections import defaultdict
13from itertools import chain
14
15from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
16from dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
17from dex.debugger.DebuggerBase import DebuggerBase
18from dex.utils.Exceptions import DebuggerException
19
20
21class ConditionalBpRange:
22    """Represents a conditional range of breakpoints within a source file descending from
23    one line to another."""
24
25    def __init__(self, expression: str, path: str, range_from: int, range_to: int, values: list):
26        self.expression = expression
27        self.path = path
28        self.range_from = range_from
29        self.range_to = range_to
30        self.conditional_values = values
31
32    def get_conditional_expression_list(self):
33        conditional_list = []
34        for value in self.conditional_values:
35            # (<expression>) == (<value>)
36            conditional_expression = '({}) == ({})'.format(self.expression, value)
37            conditional_list.append(conditional_expression)
38        return conditional_list
39
40
41class ConditionalController(DebuggerControllerBase):
42    def __init__(self, context, step_collection):
43      self.context = context
44      self.step_collection = step_collection
45      self._conditional_bps = None
46      self._watches = set()
47      self._step_index = 0
48      self._build_conditional_bps()
49      self._path_and_line_to_conditional_bp = defaultdict(list)
50      self._pause_between_steps = context.options.pause_between_steps
51      self._max_steps = context.options.max_steps
52
53    def _build_conditional_bps(self):
54        commands = self.step_collection.commands
55        self._conditional_bps = []
56        try:
57            limit_commands = commands['DexLimitSteps']
58            for lc in limit_commands:
59                conditional_bp = ConditionalBpRange(
60                  lc.expression,
61                  lc.path,
62                  lc.from_line,
63                  lc.to_line,
64                  lc.values)
65                self._conditional_bps.append(conditional_bp)
66        except KeyError:
67            raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
68
69    def _set_conditional_bps(self):
70        # When we break in the debugger we need a quick and easy way to look up
71        # which conditional bp we've breaked on.
72        for cbp in self._conditional_bps:
73            conditional_bp_list = self._path_and_line_to_conditional_bp[(cbp.path, cbp.range_from)]
74            conditional_bp_list.append(cbp)
75
76        # Set break points only on the first line of any conditional range, we'll set
77        # more break points for a range when the condition is satisfied.
78        for cbp in self._conditional_bps:
79            for cond_expr in cbp.get_conditional_expression_list():
80                self.debugger.add_conditional_breakpoint(cbp.path, cbp.range_from, cond_expr)
81
82    def _conditional_met(self, cbp):
83        for cond_expr in cbp.get_conditional_expression_list():
84            valueIR = self.debugger.evaluate_expression(cond_expr)
85            if valueIR.type_name == 'bool' and valueIR.value == 'true':
86                return True
87        return False
88
89    def _run_debugger_custom(self):
90        # TODO: Add conditional and unconditional breakpoint support to dbgeng.
91        if self.debugger.get_name() == 'dbgeng':
92            raise DebuggerException('DexLimitSteps commands are not supported by dbgeng')
93
94        self.step_collection.clear_steps()
95        self._set_conditional_bps()
96
97        for command_obj in chain.from_iterable(self.step_collection.commands.values()):
98            self._watches.update(command_obj.get_watches())
99
100        self.debugger.launch()
101        time.sleep(self._pause_between_steps)
102        while not self.debugger.is_finished:
103            while self.debugger.is_running:
104                pass
105
106            step_info = self.debugger.get_step_info(self._watches, self._step_index)
107            if step_info.current_frame:
108                self._step_index += 1
109                update_step_watches(step_info, self._watches, self.step_collection.commands)
110                self.step_collection.new_step(self.context, step_info)
111
112                loc = step_info.current_location
113                conditional_bp_key = (loc.path, loc.lineno)
114                if conditional_bp_key in self._path_and_line_to_conditional_bp:
115
116                    conditional_bps = self._path_and_line_to_conditional_bp[conditional_bp_key]
117                    for cbp in conditional_bps:
118                        if self._conditional_met(cbp):
119                            # Unconditional range should ignore first line as that's the
120                            # conditional bp we just hit and should be inclusive of final line
121                            for line in range(cbp.range_from + 1, cbp.range_to + 1):
122                                self.debugger.add_conditional_breakpoint(cbp.path, line, condition='')
123
124            # Clear any uncondtional break points at this loc.
125            self.debugger.delete_conditional_breakpoint(file_=loc.path, line=loc.lineno, condition='')
126            self.debugger.go()
127            time.sleep(self._pause_between_steps)
128