summaryrefslogtreecommitdiff
path: root/cross-project-tests/debuginfo-tests/dexter
diff options
context:
space:
mode:
authorMingming Liu <mingmingl@google.com>2025-09-10 15:25:31 -0700
committerGitHub <noreply@github.com>2025-09-10 15:25:31 -0700
commit1417dafa1db9cb1b2b09438aa9f53ea5ab6e36e2 (patch)
tree57f4b1f313c8cf74eed8819870f39c36ea263c68 /cross-project-tests/debuginfo-tests/dexter
parent898b813bc8a6d0276bf0f4769f5f2f64b34e632d (diff)
parentb8cefcb601ddaa18482555c4ff363c01a270c2fe (diff)
Merge branch 'main' into users/mingmingl-llvm/samplefdo-profile-formatusers/mingmingl-llvm/samplefdo-profile-format
Diffstat (limited to 'cross-project-tests/debuginfo-tests/dexter')
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/Commands.md44
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py4
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexContinue.py55
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexStepFunction.py38
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py158
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py27
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py238
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py23
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py3
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex-continue.cpp64
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex_step_function.cpp39
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp2
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_finish_test/limit_steps_simple.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_label_kwarg.cpp2
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_limit_steps_no_values.cpp2
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren_mline.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_dexdeclarefile.cpp2
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_mline.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type_mline.cpp7
-rw-r--r--cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp6
23 files changed, 640 insertions, 116 deletions
diff --git a/cross-project-tests/debuginfo-tests/dexter/Commands.md b/cross-project-tests/debuginfo-tests/dexter/Commands.md
index a98261a50009..afec3e6bbd00 100644
--- a/cross-project-tests/debuginfo-tests/dexter/Commands.md
+++ b/cross-project-tests/debuginfo-tests/dexter/Commands.md
@@ -13,7 +13,8 @@
* [DexDeclareFile](Commands.md#DexDeclareFile)
* [DexFinishTest](Commands.md#DexFinishTest)
* [DexCommandLine](Commands.md#DexCommandLine)
-
+* [DexStepFunction](Commands.md#DexStepFunction)
+* [DexContinue](Commands.md#DexContinue)
---
## DexExpectProgramState
DexExpectProgramState(state [,**times])
@@ -377,3 +378,44 @@ line this command is found on.
### Heuristic
[Deprecated]
+
+
+---
+## DexStepFunction
+ DexStepFunction(function_name[, **hit_count=0])
+
+ Arg list:
+ function_name (str): function to step through.
+ hit_count (int): If provided, limit the number of times the command
+ triggers.
+
+### Description
+NOTE: Only supported for DAP-based debuggers.
+
+This command controls stepping behaviour: Tell dexter to set a function
+breakpoint and step through the function after hitting it. Composes well with
+itself (you can may a callstack with multiple targets to step through) and
+`DexContinue`.
+
+---
+## DexContinue
+ DexContinue(*[expr, *values], **from_line[, **to_line, **hit_count])
+
+ Arg list:
+ function_name (str): function to step through.
+ hit_count (int): If provided, limit the number of times the command
+ triggers.
+
+### Description
+NOTE: Only supported for DAP-based debuggers.
+
+This command controls stepping behaviour: Tell dexter to set a breakpoint on
+`from_line`. When it is hit and optionally '(expr) == (values[n])' is true,
+optionally set a breakpoint on `to_line`. Then 'continue' (tell the debugger to
+run freely until a breakpoint is hit). Composed with `DexStepFunction` this
+lets you avoid stepping over certain regions (many loop iterations, for
+example). Continue-ing off the end of a `DexStepFunction` is well defined;
+stepping will resume in `DexStepFunction` targets deeper in the callstack.
+
+FIXME: hit_count should probably be inverted, like `DexLimitSteps`, to trigger
+AFTER that many hits?
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py
index 4496fdf3cb0e..1a30e0e8f375 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/ParseCommand.py
@@ -35,6 +35,8 @@ from dex.command.commands.DexLimitSteps import DexLimitSteps
from dex.command.commands.DexFinishTest import DexFinishTest
from dex.command.commands.DexUnreachable import DexUnreachable
from dex.command.commands.DexWatch import DexWatch
+from dex.command.commands.DexStepFunction import DexStepFunction
+from dex.command.commands.DexContinue import DexContinue
from dex.utils import Timer
from dex.utils.Exceptions import CommandParseError, DebuggerException
@@ -59,6 +61,8 @@ def _get_valid_commands():
DexFinishTest.get_name(): DexFinishTest,
DexUnreachable.get_name(): DexUnreachable,
DexWatch.get_name(): DexWatch,
+ DexStepFunction.get_name(): DexStepFunction,
+ DexContinue.get_name(): DexContinue,
}
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexContinue.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexContinue.py
new file mode 100644
index 000000000000..113f34bedd13
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexContinue.py
@@ -0,0 +1,55 @@
+# 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 tells dexter to set a breakpoint which, after hitting,
+signals that the debugger should 'continue' until another is hit. Continuing
+out of a function being stepped through with DexStepFunction is well defined:
+stepping will resume in other functions tracked further down the stacktrace.
+
+NOTE: Only supported for DAP-based debuggers.
+"""
+
+from dex.command.CommandBase import CommandBase
+
+
+class DexContinue(CommandBase):
+ def __init__(self, *args, **kwargs):
+ # DexContinue(*[expr, *values], **from_line[, **to_line, **hit_count])
+
+ # Optional positional args: expr, values.
+ if len(args) == 0:
+ self.expression = None
+ self.values = []
+ elif len(args) == 1:
+ raise TypeError("expected 0 or at least 2 positional arguments")
+ else:
+ self.expression = args[0]
+ self.values = [str(arg) for arg in args[1:]]
+
+ # Required keyword arg: from_line.
+ try:
+ self.from_line = kwargs.pop("from_line")
+ except:
+ raise TypeError("Missing from_line argument")
+
+ # Optional conditional args: to_line, hit_count.
+ self.to_line = kwargs.pop("to_line", None)
+ self.hit_count = kwargs.pop("hit_count", None)
+
+ if kwargs:
+ raise TypeError("unexpected named args: {}".format(", ".join(kwargs)))
+ super(DexContinue, self).__init__()
+
+ def eval(self):
+ raise NotImplementedError("DexContinue commands cannot be evaled.")
+
+ @staticmethod
+ def get_name():
+ return __class__.__name__
+
+ @staticmethod
+ def get_subcommands() -> dict:
+ return None
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexStepFunction.py b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexStepFunction.py
new file mode 100644
index 000000000000..4b80de87a1bb
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/command/commands/DexStepFunction.py
@@ -0,0 +1,38 @@
+# 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 tells dexter to set a function breakpoint and step through
+the function after hitting it.
+
+NOTE: Only supported for DAP-based debuggers.
+"""
+
+from dex.command.CommandBase import CommandBase
+
+
+class DexStepFunction(CommandBase):
+ def __init__(self, *args, **kwargs):
+ if len(args) < 1:
+ raise TypeError("expected 1 positional argument")
+ self.function = str(args[0])
+ self.hit_count = kwargs.pop("hit_count", None)
+ if kwargs:
+ raise TypeError(f"unexpected named args: {', '.join(kwargs)}")
+ super(DexStepFunction, self).__init__()
+
+ def eval(self):
+ raise NotImplementedError("DexStepFunction commands cannot be evaled.")
+
+ def get_function(self):
+ return self.function
+
+ @staticmethod
+ def get_name():
+ return __class__.__name__
+
+ @staticmethod
+ def get_subcommands() -> dict:
+ return None
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py
index 06515dd8e0b1..4e64f880487f 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DAP.py
@@ -218,6 +218,10 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
self.file_to_bp = defaultdict(list)
# { dex_breakpoint_id -> (file, line, condition) }
self.bp_info = {}
+ # { dex_breakpoint_id -> function_name }
+ self.function_bp_info = {}
+ # { dex_breakpoint_id -> instruction_reference }
+ self.instruction_bp_info = {}
# We don't rely on IDs returned directly from the debug adapter. Instead, we use dexter breakpoint IDs, and
# maintain a two-way-mapping of dex_bp_id<->dap_bp_id. This also allows us to defer the setting of breakpoints
# in the debug adapter itself until necessary.
@@ -226,6 +230,8 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
self.dex_id_to_dap_id = {}
self.dap_id_to_dex_ids = {}
self.pending_breakpoints: bool = False
+ self.pending_function_breakpoints: bool = False
+ self.pending_instruction_breakpoints: bool = False
# List of breakpoints, indexed by BP ID
# Each entry has the source file (for use in referencing desired_bps), and the DA-assigned
# ID for that breakpoint if it has one (if it has been removed or not yet created then it will be None).
@@ -288,6 +294,26 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
{"source": {"path": source}, "breakpoints": [bp.toDict() for bp in bps]},
)
+ @staticmethod
+ def make_set_function_breakpoint_request(function_names: list) -> dict:
+ # Function breakpoints may specify conditions and hit counts, though we
+ # don't use those here (though perhaps we should use native hit count,
+ # rather than emulating it ConditionalController, now that we have a
+ # shared interface (DAP)).
+ return DAP.make_request(
+ "setFunctionBreakpoints",
+ {"breakpoints": [{"name": f} for f in function_names]},
+ )
+
+ @staticmethod
+ def make_set_instruction_breakpoint_request(addrs: list) -> dict:
+ # Instruction breakpoints have additional fields we're ignoring for the
+ # moment.
+ return DAP.make_request(
+ "setInstructionBreakpoints",
+ {"breakpoints": [{"instructionReference": a} for a in addrs]},
+ )
+
############################################################################
## DAP communication & state-handling functions
@@ -355,8 +381,6 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
else:
pass
elif message["type"] == "response":
- request_seq = message["request_seq"]
- debugger_state.set_response(request_seq, message)
# TODO: We also receive a "continued" event, but it seems reasonable to set state based on either the
# response or the event, since the DAP does not specify an order in which they are sent. May need revisiting
# if there turns out to be some odd ordering issues, e.g. if we can receive messages in the order
@@ -383,6 +407,10 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
body = message.get("body")
if body:
debugger_state.capabilities.update(logger, body)
+ # Now we've done whatever we need to do with the response, tell the
+ # receiver thread we've got it.
+ request_seq = message["request_seq"]
+ debugger_state.set_response(request_seq, message)
def _colorize_dap_message(message: dict) -> dict:
colorized_message = copy.deepcopy(message)
@@ -569,6 +597,22 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
def _add_breakpoint(self, file, line):
return self._add_conditional_breakpoint(file, line, None)
+ def add_function_breakpoint(self, name: str):
+ if not self._debugger_state.capabilities.supportsFunctionBreakpoints:
+ raise DebuggerException("Debugger does not support function breakpoints")
+ new_id = self.get_next_bp_id()
+ self.function_bp_info[new_id] = name
+ self.pending_function_breakpoints = True
+ return new_id
+
+ def add_instruction_breakpoint(self, addr: str):
+ if not self._debugger_state.capabilities.supportsInstructionBreakpoints:
+ raise DebuggerException("Debugger does not support instruction breakpoints")
+ new_id = self.get_next_bp_id()
+ self.instruction_bp_info[new_id] = addr
+ self.pending_instruction_breakpoints = True
+ return new_id
+
def _add_conditional_breakpoint(self, file, line, condition):
new_id = self.get_next_bp_id()
self.file_to_bp[file].append(new_id)
@@ -576,38 +620,73 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
self.pending_breakpoints = True
return new_id
+ def _update_breakpoint_ids_after_request(self, dex_bp_ids: list, response: dict):
+ dap_bp_ids = [bp["id"] for bp in response["body"]["breakpoints"]]
+ if len(dex_bp_ids) != len(dap_bp_ids):
+ self.context.logger.error(
+ f"Sent request to set {len(dex_bp_ids)} breakpoints, but received {len(dap_bp_ids)} in response."
+ )
+ visited_dap_ids = set()
+ for i, dex_bp_id in enumerate(dex_bp_ids):
+ dap_bp_id = dap_bp_ids[i]
+ self.dex_id_to_dap_id[dex_bp_id] = dap_bp_id
+ # We take the mappings in the response as the canonical mapping, meaning that if the debug server has
+ # simply *changed* the DAP ID for a breakpoint we overwrite the existing mapping rather than adding to
+ # it, but if we receive the same DAP ID for multiple Dex IDs *then* we store a one-to-many mapping.
+ if dap_bp_id in visited_dap_ids:
+ self.dap_id_to_dex_ids[dap_bp_id].append(dex_bp_id)
+ else:
+ self.dap_id_to_dex_ids[dap_bp_id] = [dex_bp_id]
+ visited_dap_ids.add(dap_bp_id)
+
def _flush_breakpoints(self):
- if not self.pending_breakpoints:
- return
- for file in self.file_to_bp.keys():
- desired_bps = self._get_desired_bps(file)
+ # Normal and conditional breakpoints.
+ if self.pending_breakpoints:
+ self.pending_breakpoints = False
+ for file in self.file_to_bp.keys():
+ desired_bps = self._get_desired_bps(file)
+ request_id = self.send_message(
+ self.make_set_breakpoint_request(file, desired_bps)
+ )
+ result = self._await_response(request_id, 10)
+ if not result["success"]:
+ raise DebuggerException(f"could not set breakpoints for '{file}'")
+ # The debug adapter may have chosen to merge our breakpoints. From here we need to identify such cases and
+ # handle them so that our internal bookkeeping is correct.
+ dex_bp_ids = self.get_current_bps(file)
+ self._update_breakpoint_ids_after_request(dex_bp_ids, result)
+
+ # Function breakpoints.
+ if self.pending_function_breakpoints:
+ self.pending_function_breakpoints = False
+ desired_bps = list(self.function_bp_info.values())
request_id = self.send_message(
- self.make_set_breakpoint_request(file, desired_bps)
+ self.make_set_function_breakpoint_request(desired_bps)
)
result = self._await_response(request_id, 10)
if not result["success"]:
- raise DebuggerException(f"could not set breakpoints for '{file}'")
- # The debug adapter may have chosen to merge our breakpoints. From here we need to identify such cases and
- # handle them so that our internal bookkeeping is correct.
- dex_bp_ids = self.get_current_bps(file)
- dap_bp_ids = [bp["id"] for bp in result["body"]["breakpoints"]]
- if len(dex_bp_ids) != len(dap_bp_ids):
- self.context.logger.error(
- f"Sent request to set {len(dex_bp_ids)} breakpoints, but received {len(dap_bp_ids)} in response."
+ raise DebuggerException(
+ f"could not set function breakpoints: '{desired_bps}'"
)
- visited_dap_ids = set()
- for i, dex_bp_id in enumerate(dex_bp_ids):
- dap_bp_id = dap_bp_ids[i]
- self.dex_id_to_dap_id[dex_bp_id] = dap_bp_id
- # We take the mappings in the response as the canonical mapping, meaning that if the debug server has
- # simply *changed* the DAP ID for a breakpoint we overwrite the existing mapping rather than adding to
- # it, but if we receive the same DAP ID for multiple Dex IDs *then* we store a one-to-many mapping.
- if dap_bp_id in visited_dap_ids:
- self.dap_id_to_dex_ids[dap_bp_id].append(dex_bp_id)
- else:
- self.dap_id_to_dex_ids[dap_bp_id] = [dex_bp_id]
- visited_dap_ids.add(dap_bp_id)
- self.pending_breakpoints = False
+ # We expect the breakpoint order to match in request and response.
+ dex_bp_ids = list(self.function_bp_info.keys())
+ self._update_breakpoint_ids_after_request(dex_bp_ids, result)
+
+ # Address / instruction breakpoints.
+ if self.pending_instruction_breakpoints:
+ self.pending_instruction_breakpoints = False
+ desired_bps = list(self.instruction_bp_info.values())
+ request_id = self.send_message(
+ self.make_set_instruction_breakpoint_request(desired_bps)
+ )
+ result = self._await_response(request_id, 10)
+ if not result["success"]:
+ raise DebuggerException(
+ f"could not set instruction breakpoints: '{desired_bps}'"
+ )
+ # We expect the breakpoint order to match in request and response.
+ dex_bp_ids = list(self.instruction_bp_info.keys())
+ self._update_breakpoint_ids_after_request(dex_bp_ids, result)
def _confirm_triggered_breakpoint_ids(self, dex_bp_ids):
"""Can be overridden for any specific implementations that need further processing from the debug server's
@@ -619,11 +698,12 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
# Breakpoints can only have been triggered if we've hit one.
stop_reason = self._translate_stop_reason(self._debugger_state.stopped_reason)
if stop_reason != StopReason.BREAKPOINT:
- return []
+ return set()
breakpoint_ids = set(
[
dex_id
for dap_id in self._debugger_state.stopped_bps
+ if dap_id in self.dap_id_to_dex_ids
for dex_id in self.dap_id_to_dex_ids[dap_id]
]
)
@@ -632,8 +712,16 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
def delete_breakpoints(self, ids):
per_file_deletions = defaultdict(list)
for dex_bp_id in ids:
- source, _, _ = self.bp_info[dex_bp_id]
- per_file_deletions[source].append(dex_bp_id)
+ if dex_bp_id in self.bp_info:
+ source, _, _ = self.bp_info[dex_bp_id]
+ per_file_deletions[source].append(dex_bp_id)
+ elif dex_bp_id in self.function_bp_info:
+ del self.function_bp_info[dex_bp_id]
+ self.pending_function_breakpoints = True
+ elif dex_bp_id in self.instruction_bp_info:
+ del self.instruction_bp_info[dex_bp_id]
+ self.pending_instruction_breakpoints = True
+
for file, deleted_ids in per_file_deletions.items():
old_len = len(self.file_to_bp[file])
self.file_to_bp[file] = [
@@ -651,7 +739,13 @@ class DAP(DebuggerBase, metaclass=abc.ABCMeta):
""" "Set the debugger-specific params used in a launch request."""
def launch(self, cmdline):
- assert len(self.file_to_bp.keys()) > 0
+ # FIXME: Should this be a warning or exception, rather than assert?
+ assert (
+ len(self.file_to_bp)
+ + len(self.function_bp_info)
+ + len(self.instruction_bp_info)
+ > 0
+ ), "Expected at least one breakpoint before launching"
if self.context.options.target_run_args:
cmdline += shlex.split(self.context.options.target_run_args)
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
index bf7552bd5fe3..f8bee4ecb423 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerBase.py
@@ -166,6 +166,22 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
"""Returns a unique opaque breakpoint id."""
pass
+ def add_function_breakpoint(self, name):
+ """Returns a unique opaque breakpoint id.
+
+ The ID type depends on the debugger being used, but will probably be
+ an int.
+ """
+ raise NotImplementedError()
+
+ def add_instruction_breakpoint(self, addr):
+ """Returns a unique opaque breakpoint id.
+
+ The ID type depends on the debugger being used, but will probably be
+ an int.
+ """
+ raise NotImplementedError()
+
@abc.abstractmethod
def delete_breakpoints(self, ids):
"""Delete a set of breakpoints by ids.
@@ -217,6 +233,17 @@ class DebuggerBase(object, metaclass=abc.ABCMeta):
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
pass
+ def get_pc(self, frame_idx: int = 0) -> str:
+ """Get the current PC in frame at frame_idx depth.
+ frame_idx 0 is the current function.
+ """
+ r = self.evaluate_expression("$pc", frame_idx)
+ if not r.could_evaluate or r.is_optimized_away or r.is_irretrievable:
+ raise DebuggerException(
+ "evaluating '$pc' failed - possibly unsupported by the debugger"
+ )
+ return r.value
+
def _external_to_debug_path(self, path):
if not self.options.debugger_use_relative_paths:
return path
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
index c53f1419ee13..f84c4fd153c7 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/DebuggerControllers/ConditionalController.py
@@ -22,7 +22,7 @@ from dex.debugger.DebuggerControllers.DebuggerControllerBase import (
from dex.debugger.DebuggerBase import DebuggerBase
from dex.utils.Exceptions import DebuggerException
from dex.utils.Timeout import Timeout
-
+from dex.dextIR import LocIR
class BreakpointRange:
"""A range of breakpoints and a set of conditions.
@@ -51,6 +51,9 @@ class BreakpointRange:
values: list,
hit_count: int,
finish_on_remove: bool,
+ is_continue: bool = False,
+ function: str = None,
+ addr: str = None,
):
self.expression = expression
self.path = path
@@ -60,6 +63,72 @@ class BreakpointRange:
self.max_hit_count = hit_count
self.current_hit_count = 0
self.finish_on_remove = finish_on_remove
+ self.is_continue = is_continue
+ self.function = function
+ self.addr = addr
+
+ def limit_steps(
+ expression: str,
+ path: str,
+ range_from: int,
+ range_to: int,
+ values: list,
+ hit_count: int,
+ ):
+ return BreakpointRange(
+ expression,
+ path,
+ range_from,
+ range_to,
+ values,
+ hit_count,
+ False,
+ )
+
+ def finish_test(
+ expression: str, path: str, on_line: int, values: list, hit_count: int
+ ):
+ return BreakpointRange(
+ expression,
+ path,
+ on_line,
+ on_line,
+ values,
+ hit_count,
+ True,
+ )
+
+ def continue_from_to(
+ expression: str,
+ path: str,
+ from_line: int,
+ to_line: int,
+ values: list,
+ hit_count: int,
+ ):
+ return BreakpointRange(
+ expression,
+ path,
+ from_line,
+ to_line,
+ values,
+ hit_count,
+ finish_on_remove=False,
+ is_continue=True,
+ )
+
+ def step_function(function: str, path: str, hit_count: int):
+ return BreakpointRange(
+ None,
+ path,
+ None,
+ None,
+ None,
+ hit_count,
+ finish_on_remove=False,
+ is_continue=False,
+ function=function,
+ )
def has_conditions(self):
return self.expression is not None
@@ -96,34 +165,40 @@ class ConditionalController(DebuggerControllerBase):
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,
- False,
- )
- self._bp_ranges.append(bpr)
- except KeyError:
+
+ cond_controller_cmds = ["DexLimitSteps", "DexStepFunction", "DexContinue"]
+ if not any(c in commands for c in cond_controller_cmds):
raise DebuggerException(
- "Missing DexLimitSteps commands, cannot conditionally step."
+ f"No conditional commands {cond_controller_cmds}, cannot conditionally step."
)
+
+ if "DexLimitSteps" in commands:
+ for c in commands["DexLimitSteps"]:
+ bpr = BreakpointRange.limit_steps(
+ c.expression,
+ c.path,
+ c.from_line,
+ c.to_line,
+ c.values,
+ c.hit_count,
+ )
+ self._bp_ranges.append(bpr)
if "DexFinishTest" in commands:
- finish_commands = commands["DexFinishTest"]
- for ic in finish_commands:
- bpr = BreakpointRange(
- ic.expression,
- ic.path,
- ic.on_line,
- ic.on_line,
- ic.values,
- ic.hit_count + 1,
- True,
+ for c in commands["DexFinishTest"]:
+ bpr = BreakpointRange.finish_test(
+ c.expression, c.path, c.on_line, c.values, c.hit_count + 1
+ )
+ self._bp_ranges.append(bpr)
+ if "DexContinue" in commands:
+ for c in commands["DexContinue"]:
+ bpr = BreakpointRange.continue_from_to(
+ c.expression, c.path, c.from_line, c.to_line, c.values, c.hit_count
+ )
+ self._bp_ranges.append(bpr)
+ if "DexStepFunction" in commands:
+ for c in commands["DexStepFunction"]:
+ bpr = BreakpointRange.step_function(
+ c.get_function(), c.path, c.hit_count
)
self._bp_ranges.append(bpr)
@@ -138,6 +213,9 @@ class ConditionalController(DebuggerControllerBase):
bpr.path, bpr.range_from, cond_expr
)
self._leading_bp_handles[id] = bpr
+ elif bpr.function is not None:
+ id = self.debugger.add_function_breakpoint(bpr.function)
+ self._leading_bp_handles[id] = bpr
else:
# Add an unconditional breakpoint.
id = self.debugger.add_breakpoint(bpr.path, bpr.range_from)
@@ -163,6 +241,9 @@ class ConditionalController(DebuggerControllerBase):
timed_out = False
total_timeout = Timeout(self.context.options.timeout_total)
+ step_function_backtraces: list[list[str]] = []
+ self.instr_bp_ids = set()
+
while not self.debugger.is_finished:
breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint)
while self.debugger.is_running and not timed_out:
@@ -185,21 +266,25 @@ class ConditionalController(DebuggerControllerBase):
break
step_info = self.debugger.get_step_info(self._watches, self._step_index)
+ backtrace = None
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)
+ backtrace = [f.function for f in step_info.frames]
+ record_step = False
+ debugger_continue = False
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]
+ record_step = True
except KeyError:
# This is a trailing bp. Mark it for removal.
bp_to_delete.append(bp_id)
+ if bp_id in self.instr_bp_ids:
+ self.instr_bp_ids.remove(bp_id)
+ else:
+ record_step = True
continue
bpr.add_hit()
@@ -208,17 +293,94 @@ class ConditionalController(DebuggerControllerBase):
exit_desired = True
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)
+
+ if bpr.function is not None:
+ if step_info.frames:
+ # Add this backtrace to the stack. While the current
+ # backtrace matches the top of the stack we'll step,
+ # and while there's a backtrace in the stack that
+ # is a subset of the current backtrace we'll step-out.
+ if (
+ len(step_function_backtraces) == 0
+ or backtrace != step_function_backtraces[-1]
+ ):
+ step_function_backtraces.append(backtrace)
+
+ # Add an address breakpoint so we don't fall out
+ # the end of nested DexStepFunctions with a DexContinue.
+ addr = self.debugger.get_pc(frame_idx=1)
+ instr_id = self.debugger.add_instruction_breakpoint(addr)
+ # Note the breakpoint so we don't log the source location
+ # it in the trace later.
+ self.instr_bp_ids.add(instr_id)
+
+ elif bpr.is_continue:
+ debugger_continue = True
+ if bpr.range_to is not None:
+ self.debugger.add_breakpoint(bpr.path, bpr.range_to)
+
+ else:
+ # 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):
+ id = self.debugger.add_breakpoint(bpr.path, line)
# Remove any trailing or expired leading breakpoints we just hit.
self.debugger.delete_breakpoints(bp_to_delete)
+ debugger_next = False
+ debugger_out = False
+ if not debugger_continue and step_info.current_frame and step_info.frames:
+ while len(step_function_backtraces) > 0:
+ match_subtrace = False # Backtrace contains a target trace.
+ match_trace = False # Backtrace matches top of target stack.
+
+ # The top of the step_function_backtraces stack contains a
+ # backtrace that we want to step through. Check if the
+ # current backtrace ("backtrace") either matches that trace
+ # or otherwise contains it.
+ target_backtrace = step_function_backtraces[-1]
+ if len(backtrace) >= len(target_backtrace):
+ match_trace = len(backtrace) == len(target_backtrace)
+ # Check if backtrace contains target_backtrace, matching
+ # from the end (bottom of call stack) backwards.
+ match_subtrace = (
+ backtrace[-len(target_backtrace) :] == target_backtrace
+ )
+
+ if match_trace:
+ # We want to step through this function; do so and
+ # log the steps in the step trace.
+ debugger_next = True
+ record_step = True
+ break
+ elif match_subtrace:
+ # There's a function we care about buried in the
+ # current backtrace. Step-out until we get to it.
+ debugger_out = True
+ break
+ else:
+ # Drop backtraces that are not match_subtraces of the current
+ # backtrace; the functions we wanted to step through
+ # there are no longer reachable.
+ step_function_backtraces.pop()
+
+ if record_step and step_info.current_frame:
+ self._step_index += 1
+ # Record the step.
+ update_step_watches(
+ step_info, self._watches, self.step_collection.commands
+ )
+ self.step_collection.new_step(self.context, step_info)
+
if exit_desired:
break
- self.debugger.go()
+ elif debugger_next:
+ self.debugger.step_next()
+ elif debugger_out:
+ self.debugger.step_out()
+ else:
+ self.debugger.go()
time.sleep(self._pause_between_steps)
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
index 4263fdb5aa01..73b2a4dd36d0 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/debugger/lldb/LLDB.py
@@ -232,7 +232,7 @@ class LLDB(DebuggerBase):
):
stepped_to_breakpoint = True
if stepped_to_breakpoint:
- self._thread.StepInto()
+ self._process.Continue()
def go(self) -> ReturnCode:
self._process.Continue()
@@ -431,8 +431,15 @@ class LLDBDAP(DAP):
if not trace_response["success"]:
raise DebuggerException("failed to get stack frames")
stackframes = trace_response["body"]["stackFrames"]
- path = stackframes[0]["source"]["path"]
addr = stackframes[0]["instructionPointerReference"]
+ try:
+ path = stackframes[0]["source"]["path"]
+ except KeyError:
+ # We may have no path, e.g., if the module hasn't loaded or
+ # there's no debug info. If that's the case we won't have
+ # bound a source-location breakpoint here, so we can bail now.
+ return
+
if any(
self._debugger_state.bp_addr_map.get(self.dex_id_to_dap_id[dex_bp_id])
== addr
@@ -441,7 +448,7 @@ class LLDBDAP(DAP):
# Step again now to get to the breakpoint.
step_req_id = self.send_message(
self.make_request(
- "stepIn", {"threadId": self._debugger_state.thread}
+ "continue", {"threadId": self._debugger_state.thread}
)
)
response = self._await_response(step_req_id)
@@ -528,6 +535,16 @@ class LLDBDAP(DAP):
manually check conditions here."""
confirmed_breakpoint_ids = set()
for dex_bp_id in dex_bp_ids:
+ # Function and instruction breakpoints don't use conditions.
+ # FIXME: That's not a DAP restriction, so they could in future.
+ if dex_bp_id not in self.bp_info:
+ assert (
+ dex_bp_id in self.function_bp_info
+ or dex_bp_id in self.instruction_bp_info
+ )
+ confirmed_breakpoint_ids.add(dex_bp_id)
+ continue
+
_, _, cond = self.bp_info[dex_bp_id]
if cond is None:
confirmed_breakpoint_ids.add(dex_bp_id)
diff --git a/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py b/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
index c366062cec7a..693c05b97af7 100644
--- a/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
+++ b/cross-project-tests/debuginfo-tests/dexter/dex/tools/test/Tool.py
@@ -121,7 +121,8 @@ class Tool(TestToolBase):
self.context.options.source_files.extend(list(new_source_files))
- if "DexLimitSteps" in step_collection.commands:
+ cond_controller_cmds = ["DexLimitSteps", "DexStepFunction", "DexContinue"]
+ if any(c in step_collection.commands for c in cond_controller_cmds):
debugger_controller = ConditionalController(self.context, step_collection)
else:
debugger_controller = DefaultController(self.context, step_collection)
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex-continue.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex-continue.cpp
new file mode 100644
index 000000000000..2d5cbe04569d
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex-continue.cpp
@@ -0,0 +1,64 @@
+// Purpose:
+// Test \DexStepFunction usage with \DexContinue. Continuing out of `c`
+// should result in stepping resuming in `a` (check there's no issue when
+// `b` is inlined). Then continuing out of `a` should run on to `f` where
+// stepping resumes again. Stepping out of `f` into `main`, run free
+// again until the program exits.
+//
+// This command is only implemented for debuggers with DAP support.
+// UNSUPPORTED: system-windows
+//
+// RUN: %dexter_regression_test_cxx_build %s -o %t
+// RUN: %dexter_regression_test_run -v --binary %t -- %s 2>&1 | FileCheck %s
+
+int g = 0;
+int c(int) {
+ ++g;
+ ++g;
+ ++g;
+ ++g;
+ ++g;
+ return 0;
+}
+
+__attribute__((always_inline))
+int b(int) {
+ ++g;
+ return c(g);
+}
+
+int a(int) {
+ ++g;
+ b(g);
+ ++g;
+ return g;
+}
+
+void f() {
+ ++g;
+}
+
+int main() {
+ int x = a(g);
+ f();
+ return x;
+}
+
+// DexStepFunction('c')
+// DexContinue(from_line=17, to_line=19)
+// DexContinue(from_line=20)
+// DexStepFunction('a')
+// DexContinue(from_line=33)
+// DexStepFunction('f')
+
+// CHECK: ## BEGIN ##
+// CHECK-NEXT: . [0, "a(int)", "{{.*}}dex-continue.cpp", 31, 3, "StopReason.BREAKPOINT", "StepKind.FUNC", []]
+// CHECK-NEXT: . [1, "a(int)", "{{.*}}dex-continue.cpp", 32, 5, "StopReason.STEP", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . . . [2, "c(int)", "{{.*}}dex-continue.cpp", 16, 3, "StopReason.BREAKPOINT", "StepKind.FUNC", []]
+// CHECK-NEXT: . . . [3, "c(int)", "{{.*}}dex-continue.cpp", 17, 3, "StopReason.BREAKPOINT", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . . . [4, "c(int)", "{{.*}}dex-continue.cpp", 19, 3, "StopReason.BREAKPOINT", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . . . [5, "c(int)", "{{.*}}dex-continue.cpp", 20, 3, "StopReason.BREAKPOINT", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . [6, "a(int)", "{{.*}}dex-continue.cpp", 33, 3, "StopReason.BREAKPOINT", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . [7, "f()", "{{.*}}dex-continue.cpp", 38, 3, "StopReason.BREAKPOINT", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: . [8, "f()", "{{.*}}dex-continue.cpp", 39, 1, "StopReason.STEP", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT: ## END (9 steps) ##
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex_step_function.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex_step_function.cpp
new file mode 100644
index 000000000000..900e10b64a96
--- /dev/null
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/control/dex_step_function.cpp
@@ -0,0 +1,39 @@
+// Purpose:
+// \DexStepFunction smoke test. Only steps in a and c should be logged.
+//
+// This command is only implemented for debuggers with DAP support.
+// UNSUPPORTED: system-windows
+//
+// RUN: %dexter_regression_test_cxx_build %s -o %t
+// RUN: %dexter_regression_test_run -v --binary %t -- %s 2>&1 | FileCheck %s
+
+int g = 0;
+int c(int) {
+ ++g;
+ return 0;
+}
+
+int b(int) {
+ ++g;
+ return c(g);
+}
+
+int a(int) {
+ ++g;
+ return b(g);
+}
+
+int main() {
+ return a(g);
+}
+
+// DexStepFunction('a')
+// DexStepFunction('c')
+
+// CHECK: ## BEGIN ##
+// CHECK-NEXT:. [0, "a(int)", "{{.*}}dex_step_function.cpp", 22, 3, "StopReason.BREAKPOINT", "StepKind.FUNC", []]
+// CHECK-NEXT:. [1, "a(int)", "{{.*}}dex_step_function.cpp", 23, 12, "StopReason.STEP", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT:. . . [2, "c(int)", "{{.*}}dex_step_function.cpp", 12, 3, "StopReason.BREAKPOINT", "StepKind.FUNC", []]
+// CHECK-NEXT:. . . [3, "c(int)", "{{.*}}dex_step_function.cpp", 13, 3, "StopReason.STEP", "StepKind.VERTICAL_FORWARD", []]
+// CHECK-NEXT:. [4, "a(int)", "{{.*}}dex_step_function.cpp", 23, 3, "StopReason.STEP", "StepKind.HORIZONTAL_BACKWARD", []]
+// CHECK-NEXT: ## END (5 steps) ##
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp
index 2bb83850e2a1..0f4463338bba 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_declare_address/expression_address.cpp
@@ -14,4 +14,4 @@ int main() {
// DexDeclareAddress('x_addr', '&x', on_line=ref('test_line'))
// DexExpectWatchValue('&x', address('x_addr'), on_line=ref('test_line'))
-// DexExpectWatchValue('&y', address('x_addr'), on_line=ref('test_line'))
+// DexExpectWatchValue('y', address('x_addr'), on_line=ref('test_line'))
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_finish_test/limit_steps_simple.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_finish_test/limit_steps_simple.cpp
index 27505d5a3f5a..923f596ab84e 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_finish_test/limit_steps_simple.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/commands/perfect/dex_finish_test/limit_steps_simple.cpp
@@ -12,10 +12,11 @@
// CHECK: limit_steps_simple.cpp
int main() {
- int x = 0; // DexLabel('start')
- x = 1;
+ int x = 0;
+ x = 1; // DexLabel('start')
x = 2; // DexLabel('finish_line')
-} // DexLabel('finish')
+ return 0; // DexLabel('finish')
+}
// DexLimitSteps(from_line=ref('start'), to_line=ref('finish'))
// DexFinishTest(on_line=ref('finish_line'))
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_label_kwarg.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_label_kwarg.cpp
index b0bd50a248d1..da1573525253 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_label_kwarg.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_label_kwarg.cpp
@@ -2,7 +2,7 @@
// Check that bad keyword args in \DexLabel are reported.
// Use --binary switch to trick dexter into skipping the build step.
//
-// RUN: not %dexter_base test --binary %s --debugger 'lldb' -- %s | FileCheck %s
+// RUN: not %dexter_base test --binary %s %dexter_regression_test_debugger_args -- %s | FileCheck %s
// CHECK: parser error:{{.*}}err_label_kwarg.cpp(8): unexpected named args: bad_arg
// DexLabel('test', bad_arg=0)
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_limit_steps_no_values.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_limit_steps_no_values.cpp
index 64e41495ece1..978450eeb8cd 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_limit_steps_no_values.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_limit_steps_no_values.cpp
@@ -3,7 +3,7 @@
// in a \DexLimitSteps command results in a useful error message.
// Use --binary switch to trick dexter into skipping the build step.
//
-// RUN: not %dexter_base test --binary %s --debugger 'lldb' -- %s | FileCheck %s
+// RUN: not %dexter_base test --binary %s %dexter_regression_test_debugger_args -- %s | FileCheck %s
// CHECK: parser error:{{.*}}err_limit_steps_no_values.cpp(9): expected 0 or at least 2 positional arguments
// DexLimitSteps('test')
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren.cpp
index 5a35b3a512be..e80b34da24ac 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger 'lldb' \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_paren.cpp(22): Unbalanced parenthesis starting here
+// CHECK:parser error:{{.*}}err_paren.cpp(19): Unbalanced parenthesis starting here
// CHECK:// {{Dex}}ExpectWatchValue(
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren_mline.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren_mline.cpp
index 0044b3b6eff0..8d5a9b057599 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren_mline.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_paren_mline.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger "lldb" \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_paren_mline.cpp(23): Unbalanced parenthesis starting here
+// CHECK:parser error:{{.*}}err_paren_mline.cpp(20): Unbalanced parenthesis starting here
// CHECK:{{Dex}}ExpectWatchValue(
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax.cpp
index 599202544213..7e019df26e9b 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger "lldb" \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_syntax.cpp(21): invalid syntax
+// CHECK:parser error:{{.*}}err_syntax.cpp(18): invalid syntax
// CHECK:// {{Dex}}ExpectWatchValue(,'a', 3, 3, 3, 3, on_line=0)
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_dexdeclarefile.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_dexdeclarefile.cpp
index 40cc1581f85f..0fdf255eded8 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_dexdeclarefile.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_dexdeclarefile.cpp
@@ -3,7 +3,7 @@
// they appeared in rather than the current declared file.
//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger 'lldb' -v -- %s \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args -v -- %s \
// RUN: | FileCheck %s --implicit-check-not=FAIL-FILENAME-MATCH
// CHECK: err_syntax_dexdeclarefile.cpp(14): Undeclared address: 'not_been_declared'
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_mline.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_mline.cpp
index 71b23a2a3a8b..342f2a53010e 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_mline.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_syntax_mline.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger "lldb" \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_syntax_mline.cpp(24): invalid syntax
+// CHECK:parser error:{{.*}}err_syntax_mline.cpp(21): invalid syntax
// CHECK: ,'a', 3, 3, 3, 3, on_line=0)
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type.cpp
index 264515496f1c..286530410193 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger "lldb" \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_type.cpp(21): expected at least two args
+// CHECK:parser error:{{.*}}err_type.cpp(18): expected at least two args
// CHECK:// {{Dex}}ExpectWatchValue()
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type_mline.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type_mline.cpp
index 5cbcd2d88808..1062d2816b39 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type_mline.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/err_type_mline.cpp
@@ -4,14 +4,11 @@
// Check directives are in check.txt to prevent dexter reading any embedded
// commands.
//
-// Note: Despite using 'lldb' as the debugger, lldb is not actually required
-// as the test should finish before lldb would be invoked.
-//
// RUN: %dexter_regression_test_cxx_build %s -o %t
-// RUN: not %dexter_base test --binary %t --debugger "lldb" \
+// RUN: not %dexter_base test --binary %t %dexter_regression_test_debugger_args \
// RUN: -v -- %s | FileCheck %s --match-full-lines --strict-whitespace
//
-// CHECK:parser error:{{.*}}err_type_mline.cpp(22): expected at least two args
+// CHECK:parser error:{{.*}}err_type_mline.cpp(19): expected at least two args
// CHECK:{{Dex}}ExpectWatchValue(
// CHECK: ^
diff --git a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp
index af24c5d8e572..edbafdc6bfc1 100644
--- a/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp
+++ b/cross-project-tests/debuginfo-tests/dexter/feature_tests/subtools/test/source-root-dir.cpp
@@ -2,10 +2,8 @@
// XFAIL:*
// RUN: %dexter_regression_test_cxx_build \
// RUN: -fdebug-prefix-map=%S=/changed %s -o %t
-// RUN: %dexter --fail-lt 1.0 -w \
-// RUN: --binary %t \
-// RUN: --debugger %dexter_regression_test_debugger \
-// RUN: --source-root-dir=%S --debugger-use-relative-paths -- %s
+// RUN: %dexter_regression_test_run \
+// RUN: --binary %t --source-root-dir=%S --debugger-use-relative-paths -- %s
#include <stdio.h>
int main() {