--- /dev/null
+diff --git a/debuginfo-tests/dexter/Commands.md b/debuginfo-tests/dexter/Commands.md
+index c30a0d7214c..2e2fecfed92 100644
+--- a/debuginfo-tests/dexter/Commands.md
++++ b/debuginfo-tests/dexter/Commands.md
+@@ -173,6 +173,34 @@ Expect the source line this is found on will never be stepped on to.
+ [TODO]
+
+
++----
++## DexLimitSteps
++ DexLimitSteps(expr, *values [, **from_line=1],[,**to_line=Max]
++ [,**on_line])
++
++ Args:
++ expr (str): variable or value to compare.
++
++ Arg list:
++ values (str): At least one potential value the expr may evaluate to.
++
++ Keyword args:
++ from_line (int): Define the start of the limited step range.
++ to_line (int): Define the end of the limited step range.
++ on_line (int): Define a range with length 1 starting and ending on the
++ same line.
++
++### Description
++Define a limited stepping range that is predicated on a condition. When
++'(expr) == (values[n])', set a range of temporary, unconditional break points within
++the test file defined by the range from_line and to_line or on_line.
++
++The condition is only evaluated on the line 'from_line' or 'on_line'. If the
++condition is not true at the start of the range, the whole range is ignored.
++
++DexLimitSteps commands are useful for reducing the amount of steps gathered in
++large test cases that would normally take much longer to complete.
++
+ ----
+ ## DexLabel
+ DexLabel(name)
+diff --git a/debuginfo-tests/dexter/dex/command/ParseCommand.py b/debuginfo-tests/dexter/dex/command/ParseCommand.py
+index 4cc9ae12592..8246ea9e3cf 100644
+--- a/debuginfo-tests/dexter/dex/command/ParseCommand.py
++++ b/debuginfo-tests/dexter/dex/command/ParseCommand.py
+@@ -24,6 +24,7 @@ from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder
+ from dex.command.commands.DexExpectWatchType import DexExpectWatchType
+ from dex.command.commands.DexExpectWatchValue import DexExpectWatchValue
+ from dex.command.commands.DexLabel import DexLabel
++from dex.command.commands.DexLimitSteps import DexLimitSteps
+ from dex.command.commands.DexUnreachable import DexUnreachable
+ from dex.command.commands.DexWatch import DexWatch
+ from dex.utils import Timer
+@@ -42,6 +43,7 @@ def _get_valid_commands():
+ DexExpectWatchType.get_name() : DexExpectWatchType,
+ DexExpectWatchValue.get_name() : DexExpectWatchValue,
+ DexLabel.get_name() : DexLabel,
++ DexLimitSteps.get_name() : DexLimitSteps,
+ DexUnreachable.get_name() : DexUnreachable,
+ DexWatch.get_name() : DexWatch
+ }
+diff --git a/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py b/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py
+new file mode 100644
+index 00000000000..d66401b5599
+--- /dev/null
++++ b/debuginfo-tests/dexter/dex/command/commands/DexLimitSteps.py
+@@ -0,0 +1,54 @@
++# 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
++"""A Command that enables test writers to specify a limited number of break
++points using an start condition and range.
++"""
++
++from dex.command.CommandBase import CommandBase
++
++class DexLimitSteps(CommandBase):
++ def __init__(self, *args, **kwargs):
++ self.expression = args[0]
++ self.values = [str(arg) for arg in args[1:]]
++ try:
++ on_line = kwargs.pop('on_line')
++ self.from_line = on_line
++ self.to_line = on_line
++ except KeyError:
++ self.from_line = kwargs.pop('from_line', 1)
++ self.to_line = kwargs.pop('to_line', 999999)
++ if kwargs:
++ raise TypeError('unexpected named args: {}'.format(
++ ', '.join(kwargs)))
++ super(DexLimitSteps, self).__init__()
++
++ def resolve_label(self, label_line_pair):
++ label, lineno = label_line_pair
++ if isinstance(self.from_line, str):
++ if self.from_line == label:
++ self.from_line = lineno
++ if isinstance(self.to_line, str):
++ if self.to_line == label:
++ self.to_line = lineno
++
++ def has_labels(self):
++ return len(self.get_label_args()) > 0
++
++ def get_label_args(self):
++ return [label for label in (self.from_line, self.to_line)
++ if isinstance(label, str)]
++
++ def eval(self):
++ raise NotImplementedError('DexLimitSteps commands cannot be evaled.')
++
++ @staticmethod
++ def get_name():
++ return __class__.__name__
++
++ @staticmethod
++ def get_subcommands() -> dict:
++ return None
+diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
+index 2261396b94b..12f4f4ab7a0 100644
+--- a/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
++++ b/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
+@@ -120,6 +120,14 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
+ def add_breakpoint(self, file_, line):
+ pass
+
++ @abc.abstractmethod
++ def add_conditional_breakpoint(self, file_, line, condition):
++ pass
++
++ @abc.abstractmethod
++ def delete_conditional_breakpoint(self, file_, line, condition):
++ pass
++
+ @abc.abstractmethod
+ def launch(self):
+ pass
+diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
+new file mode 100644
+index 00000000000..4e4327b53f8
+--- /dev/null
++++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
+@@ -0,0 +1,127 @@
++# 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_bps = None
++ self._watches = set()
++ self._step_index = 0
++ self._build_conditional_bps()
++ self._path_and_line_to_conditional_bp = defaultdict(list)
++ self._pause_between_steps = context.options.pause_between_steps
++ self._max_steps = context.options.max_steps
++
++ def _build_conditional_bps(self):
++ commands = self.step_collection.commands
++ self._conditional_bps = []
++ 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_bps.append(conditional_bp)
++ except KeyError:
++ raise DebuggerException('Missing DexLimitSteps commands, cannot conditionally step.')
++
++ def _set_conditional_bps(self):
++ # When we break in the debugger we need a quick and easy way to look up
++ # which conditional bp we've breaked on.
++ for cbp in self._conditional_bps:
++ conditional_bp_list = self._path_and_line_to_conditional_bp[(cbp.path, cbp.range_from)]
++ conditional_bp_list.append(cbp)
++
++ # Set break points only on the first line of any conditional range, we'll set
++ # more break points for a range when the condition is satisfied.
++ for cbp in self._conditional_bps:
++ for cond_expr in cbp.get_conditional_expression_list():
++ self.debugger.add_conditional_breakpoint(cbp.path, cbp.range_from, cond_expr)
++
++ 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)
++
++ loc = step_info.current_location
++ conditional_bp_key = (loc.path, loc.lineno)
++ if conditional_bp_key in self._path_and_line_to_conditional_bp:
++
++ conditional_bps = self._path_and_line_to_conditional_bp[conditional_bp_key]
++ for cbp in conditional_bps:
++ if self._conditional_met(cbp):
++ # Unconditional range should ignore first line as that's the
++ # conditional bp we just hit and should be inclusive of final line
++ for line in range(cbp.range_from + 1, cbp.range_to + 1):
++ self.debugger.add_conditional_breakpoint(cbp.path, line, condition='')
++
++ # Clear any uncondtional break points at this loc.
++ self.debugger.delete_conditional_breakpoint(file_=loc.path, line=loc.lineno, condition='')
++ self.debugger.go()
++ time.sleep(self._pause_between_steps)
+diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py
+new file mode 100644
+index 00000000000..adac7674aff
+--- /dev/null
++++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ControllerHelpers.py
+@@ -0,0 +1,37 @@
++# 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
++
++import os
++from itertools import chain
++
++def in_source_file(source_files, 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 source_files)
++
++def update_step_watches(step_info, watches, commands):
++ watch_cmds = ['DexUnreachable', 'DexExpectStepOrder']
++ towatch = chain.from_iterable(commands[x]
++ for x in watch_cmds
++ if x in 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
+diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
+index ff98baa2d0e..87b13fc7f3a 100644
+--- a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
++++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DebuggerControllerBase.py
+@@ -4,7 +4,7 @@
+ # 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."""
++"""Abstract Base class for controlling debuggers."""
+
+ import abc
+
+diff --git a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
+index 0077a19e601..c41a3eff0d3 100644
+--- a/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
++++ b/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/DefaultController.py
+@@ -4,61 +4,37 @@
+ # 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."""
++"""Default 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
++from dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
++from dex.utils.Exceptions import DebuggerException, LoadDebuggerException
+
+ class DefaultController(DebuggerControllerBase):
+ def __init__(self, context, step_collection):
+ self.context = context
+ self.step_collection = step_collection
++ self.source_files = self.context.options.source_files
+ 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)
++ try:
++ self.debugger.add_breakpoint(s, line)
++ except DebuggerException:
++ raise LoadDebuggerException(DebuggerException.msg)
+
+ 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()):
+@@ -76,10 +52,10 @@ class DefaultController(DebuggerControllerBase):
+ step_info = self.debugger.get_step_info(self.watches, self.step_index)
+
+ if step_info.current_frame:
+- self._update_step_watches(step_info)
++ update_step_watches(step_info, self.watches, self.step_collection.commands)
+ self.step_collection.new_step(self.context, step_info)
+
+- if self._in_source_file(step_info):
++ if in_source_file(self.source_files, step_info):
+ self.debugger.step()
+ else:
+ self.debugger.go()
+diff --git a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
+index 0afc748aecb..d812fd974f7 100644
+--- a/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
++++ b/debuginfo-tests/dexter/dex/debugger/dbgeng/dbgeng.py
+@@ -77,11 +77,21 @@ class DbgEng(DebuggerBase):
+ self.client.Control.RemoveBreakpoint(x)
+
+ def add_breakpoint(self, file_, line):
+- # This is something to implement in the future -- as it stands, Dexter
+- # doesn't test for such things as "I can set a breakpoint on this line".
+- # This is only called AFAICT right now to ensure we break on every step.
++ # Breakpoint setting/deleting is not supported by dbgeng at this moment
++ # but is something that should be considered in the future.
++ # TODO: this method is called in the DefaultController but has no effect.
+ pass
+
++ def add_conditional_breakpoint(self, file_, line, condition):
++ # breakpoint setting/deleting is not supported by dbgeng at this moment
++ # but is something that should be considered in the future.
++ raise NotImplementedError('add_conditional_breakpoint is not yet implemented by dbgeng')
++
++ def delete_conditional_breakpoint(self, file_, line, condition):
++ # breakpoint setting/deleting is not supported by dbgeng at this moment
++ # but is something that should be considered in the future.
++ raise NotImplementedError('delete_conditional_breakpoint is not yet implemented by dbgeng')
++
+ def launch(self):
+ # We are, by this point, already launched.
+ self.step_info = probe_process.probe_state(self.client)
+diff --git a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
+index a943431c888..c7bb74681d9 100644
+--- a/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
++++ b/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
+@@ -105,9 +105,48 @@ class LLDB(DebuggerBase):
+
+ def add_breakpoint(self, file_, line):
+ if not self._target.BreakpointCreateByLocation(file_, line):
+- raise LoadDebuggerException(
++ raise DebuggerException(
+ 'could not add breakpoint [{}:{}]'.format(file_, line))
+
++ def add_conditional_breakpoint(self, file_, line, condition):
++ bp = self._target.BreakpointCreateByLocation(file_, line)
++ if bp:
++ bp.SetCondition(condition)
++ else:
++ raise DebuggerException(
++ 'could not add breakpoint [{}:{}]'.format(file_, line))
++
++ def delete_conditional_breakpoint(self, file_, line, condition):
++ bp_count = self._target.GetNumBreakpoints()
++ bps = [self._target.GetBreakpointAtIndex(ix) for ix in range(0, bp_count)]
++
++ for bp in bps:
++ bp_cond = bp.GetCondition()
++ bp_cond = bp_cond if bp_cond is not None else ''
++
++ if bp_cond != condition:
++ continue
++
++ # If one of the bound bp locations for this bp is bound to the same
++ # line in file_ above, then delete the entire parent bp and all
++ # bp locs.
++ # https://lldb.llvm.org/python_reference/lldb.SBBreakpoint-class.html
++ for breakpoint_location in bp:
++ sb_address = breakpoint_location.GetAddress()
++
++ sb_line_entry = sb_address.GetLineEntry()
++ bl_line = sb_line_entry.GetLine()
++
++ sb_file_entry = sb_line_entry.GetFileSpec()
++ bl_dir = sb_file_entry.GetDirectory()
++ bl_file_name = sb_file_entry.GetFilename()
++
++ bl_file_path = os.path.join(bl_dir, bl_file_name)
++
++ if bl_file_path == file_ and bl_line == line:
++ self._target.BreakpointDelete(bp.GetID())
++ break
++
+ def launch(self):
+ self._process = self._target.LaunchSimple(None, None, os.getcwd())
+ if not self._process or self._process.GetNumThreads() == 0:
+diff --git a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
+index b9816f84f72..40a902bd205 100644
+--- a/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
++++ b/debuginfo-tests/dexter/dex/debugger/visualstudio/VisualStudio.py
+@@ -82,6 +82,9 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
+
+ @property
+ def _location(self):
++ #TODO: Find a better way of determining path, line and column info
++ # that doesn't require reading break points. This method requires
++ # all lines to have a break point on them.
+ bp = self._debugger.BreakpointLastHit
+ return {
+ 'path': getattr(bp, 'File', None),
+@@ -111,8 +114,20 @@ class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abst
+ def add_breakpoint(self, file_, line):
+ self._debugger.Breakpoints.Add('', file_, line)
+
++ def add_conditional_breakpoint(self, file_, line, condition):
++ column = 1
++ self._debugger.Breakpoints.Add('', file_, line, column, condition)
++
++ def delete_conditional_breakpoint(self, file_, line, condition):
++ for bp in self._debugger.Breakpoints:
++ for bound_bp in bp.Children:
++ if (bound_bp.File == file_ and bound_bp.FileLine == line and
++ bound_bp.Condition == condition):
++ bp.Delete()
++ break
++
+ def launch(self):
+- self.step()
++ self._fn_go()
+
+ def step(self):
+ self._fn_step()
+diff --git a/debuginfo-tests/dexter/dex/tools/test/Tool.py b/debuginfo-tests/dexter/dex/tools/test/Tool.py
+index a615c8cad90..43191fd44bd 100644
+--- a/debuginfo-tests/dexter/dex/tools/test/Tool.py
++++ b/debuginfo-tests/dexter/dex/tools/test/Tool.py
+@@ -16,6 +16,7 @@ from dex.builder import run_external_build_script
+ from dex.command.ParseCommand import get_command_infos
+ from dex.debugger.Debuggers import run_debugger_subprocess
+ from dex.debugger.DebuggerControllers.DefaultController import DefaultController
++from dex.debugger.DebuggerControllers.ConditionalController import ConditionalController
+ from dex.dextIR.DextIR import DextIR
+ from dex.heuristic import Heuristic
+ from dex.tools import TestToolBase
+@@ -136,9 +137,15 @@ class Tool(TestToolBase):
+ executable_path=self.context.options.executable,
+ source_paths=self.context.options.source_files,
+ dexter_version=self.context.version)
++
+ step_collection.commands = get_command_infos(
+ self.context.options.source_files)
+- debugger_controller = DefaultController(self.context, step_collection)
++
++ if 'DexLimitSteps' in step_collection.commands:
++ debugger_controller = ConditionalController(self.context, step_collection)
++ else:
++ debugger_controller = DefaultController(self.context, step_collection)
++
+ return debugger_controller
+
+ def _get_steps(self, builderIR):
+diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp
+new file mode 100644
+index 00000000000..45683fced2d
+--- /dev/null
++++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_check_json_step_count.cpp
+@@ -0,0 +1,20 @@
++// Purpose:
++// Check number of step lines are correctly reported in json output.
++//
++// REQUIRES: system-linux
++//
++// RUN: %dexter_regression_test --verbose -- %s | FileCheck %s
++// CHECK: limit_steps_check_json_step_count.cpp
++// CHECK: ## BEGIN ##
++// CHECK-COUNT-3: json_step_count.cpp",
++
++int main() {
++ int result = 0;
++ for(int ix = 0; ix != 10; ++ix) {
++ int index = ix;
++ result += index; // DexLabel('check')
++ }
++}
++
++// DexExpectWatchValue('index', 2, 7, 9, on_line='check')
++// DexLimitSteps('ix', 2, 7, 9, on_line='check')
+diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp
+new file mode 100644
+index 00000000000..5946fa6ba46
+--- /dev/null
++++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_loop.cpp
+@@ -0,0 +1,20 @@
++// Purpose:
++// Check the DexLimit steps only gathers step info for 2 iterations of a
++// for loop.
++//
++// REQUIRES: system-linux
++//
++// RUN: %dexter_regression_test -- %s | FileCheck %s
++// CHECK: limit_steps_expect_loop.cpp:
++
++int main(const int argc, const char * argv[]) {
++ unsigned int sum = 1;
++ for(unsigned int ix = 0; ix != 5; ++ix) {
++ unsigned thing_to_add = ix + ix - ix; // DexLabel('start')
++ sum += ix; // DexLabel('end')
++ }
++ return sum;
++}
++
++// DexLimitSteps('ix', 0, 3, from_line='start', to_line='end')
++// DexExpectWatchValue('ix', 0, 3, from_line='start', to_line='end')
+diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp
+new file mode 100644
+index 00000000000..2715e28d66b
+--- /dev/null
++++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_expect_value.cpp
+@@ -0,0 +1,18 @@
++// Purpose:
++// Ensure that limited stepping breaks for all expected values.
++//
++// REQUIRES: system-linux
++//
++// RUN: %dexter_regression_test -- %s | FileCheck %s
++// CHECK: limit_steps_expect_value.cpp
++
++int main() {
++ int i = 0;
++ i = 1; // DexLabel('from')
++ i = 2;
++ i = 3;
++ return 0; // DexLabel('long_range')
++}
++
++// DexLimitSteps('i', '0', from_line='from', to_line='long_range')
++// DexExpectWatchValue('i', 0, 1, 2, 3, from_line='from', to_line='long_range')
+diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp
+new file mode 100644
+index 00000000000..3200fe0979b
+--- /dev/null
++++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_overlapping_ranges.cpp
+@@ -0,0 +1,36 @@
++// Purpose:
++// Ensure that multiple overlapping \DexLimitSteps ranges do not interfere.
++//
++// REQUIRES: system-linux
++//
++// RUN: %dexter_regression_test -- %s | FileCheck %s
++// CHECK: limit_steps_overlapping_ranges.cpp
++
++int main() {
++ int val1;
++ int val2;
++ int placeholder;
++ for (int ix = 0; ix != 10; ++ix) {
++ placeholder=val1+val2; // DexLabel('from')
++ if (ix == 0) {
++ val1 = ix;
++ val2 = ix; // DexLabel('val1_check')
++ placeholder=val1+val2; // DexLabel('val1_check_to')
++ }
++ else if (ix == 2) {
++ val2 = ix;
++ val1 = ix; // DexLabel('val2_check')
++ placeholder=val1+val2; // DexLabel('val2_check_to')
++ }
++ placeholder=val1+val2; // DexLabel('to')
++ }
++ return val1 + val2;
++}
++
++// DexExpectWatchValue('ix', 0, 2, 5, from_line='from', to_line='to')
++// DexExpectWatchValue('val1', 0, from_line='val1_check', to_line='val1_check_to')
++// DexExpectWatchValue('val2', 2, from_line='val2_check', to_line='val2_check_to')
++
++// DexLimitSteps('ix', 5, from_line='from', to_line='to')
++// DexLimitSteps('val1', 0, from_line='val1_check', to_line='val1_check_to')
++// DexLimitSteps('val2', 2, from_line='val2_check', to_line='val2_check_to')
+diff --git a/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp
+new file mode 100644
+index 00000000000..060ff0d5fe7
+--- /dev/null
++++ b/debuginfo-tests/dexter/feature_tests/commands/perfect/limit_steps/limit_steps_same_line_conditional.cpp
+@@ -0,0 +1,26 @@
++// Purpose:
++// Test that LimitStep commands can exist on the same from line.
++//
++// REQUIRES: system-linux
++//
++// RUN: %dexter_regression_test -- %s | FileCheck %s
++// CHECK: limit_steps_same_line_conditional.cpp
++
++int main() {
++ int val1 = 0;
++
++ int placeholder;
++ for(int ix = 0; ix != 4; ++ix) {
++ val1 = ix;
++ placeholder = ix; // DexLabel('from')
++ placeholder = ix;
++ val1 += 2; // DexLabel('to')
++ placeholder = ix; // DexLabel('extended_to')
++ }
++ return val1 + placeholder;
++}
++
++// DexExpectWatchValue('val1', 0, 1, 3, from_line='from', to_line='extended_to')
++
++// DexLimitSteps('ix', 0, from_line='from', to_line='to')
++// DexLimitSteps('ix', 1, from_line='from', to_line='extended_to')