CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
165 lines
6.9 KiB
Python
165 lines
6.9 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
|
|
"""Debugger Controller Class for DExTer, responsible for driving a debugger session, invoking debugger actions and
|
|
recording debugger output."""
|
|
|
|
|
|
from enum import Enum
|
|
import time
|
|
from typing import Optional
|
|
|
|
from dex.debugger.DebuggerControllers.DebuggerControllerBase import (
|
|
DebuggerControllerBase,
|
|
)
|
|
from dex.debugger.DebuggerBase import DebuggerBase
|
|
from dex.debugger.DAP import DAP
|
|
from dex.evaluation.StateMatch import get_active_where_expects
|
|
from dex.test_script.Nodes import Where
|
|
from dex.test_script.Script import DexterScript, Scope
|
|
from dex.tools import Context
|
|
from dex.utils.Timeout import Timeout
|
|
from dex.dextIR import DextIR, StepIR
|
|
|
|
|
|
class DebuggerAction(Enum):
|
|
STEP_OVER = 0
|
|
STEP_OUT = 1
|
|
CONTINUE = 2
|
|
EXIT = 3
|
|
|
|
|
|
class ScriptDebuggerController(DebuggerControllerBase):
|
|
"""Uses a Dexter Script to drive a debugger session, using "where" nodes to make breakpoint/stepping decisions, and
|
|
"expect" nodes to evaluate variables."""
|
|
|
|
def __init__(self, context: Context, step_collection: DextIR):
|
|
super().__init__(context, step_collection)
|
|
self._step_index = 0
|
|
self._pause_between_steps = context.options.pause_between_steps
|
|
self._max_steps = context.options.max_steps
|
|
assert step_collection.script is not None
|
|
self.script: DexterScript = step_collection.script
|
|
|
|
# We may need to pickle this debugger controller after running the
|
|
# debugger. Debuggers are not picklable objects, so this starts as None
|
|
# and will be set back to None after we finish running the debugger.
|
|
self.debugger: DebuggerBase = None # type: ignore
|
|
|
|
def add_where_entry_bp(self, where: Where, default_file: Optional[str] = None):
|
|
"""Adds a breakpoint to catch when we enter the given !where node."""
|
|
added_ids = []
|
|
if where.function:
|
|
if where.lines:
|
|
raise NotImplementedError(
|
|
f"Implementation does not currently handle !where with function and lines: {where}"
|
|
)
|
|
added_ids.append(self.debugger.add_function_breakpoint(where.function))
|
|
elif where.lines:
|
|
# We prefer an explicit file, but we make a special allowance for root !where nodes, which are assumed to
|
|
# refer to the script file if the file is omitted.
|
|
file = where.file or default_file
|
|
assert file, "Cannot set line breakpoints without a valid file!"
|
|
# If this Where covers a range of lines, we breakpoint each of them to ensure that we don't miss any lines.
|
|
for line in where.get_lines():
|
|
added_ids.append(self.debugger.add_breakpoint(file, line))
|
|
for id in added_ids:
|
|
self.context.logger.note(f"Added Entry BP {id} for {where}")
|
|
|
|
def _init_bps(self):
|
|
for where in self.script.root_wheres:
|
|
self.add_where_entry_bp(where, self.script.root_scope.file)
|
|
|
|
def _run_debugger_custom(self, cmdline):
|
|
if not isinstance(self.debugger, DAP):
|
|
raise NotImplementedError(
|
|
"Only DAP-based debuggers currently supported in structured scripts."
|
|
)
|
|
|
|
self.step_collection.clear_steps()
|
|
|
|
script: DexterScript = self.script
|
|
self._init_bps()
|
|
|
|
self.debugger.launch(cmdline)
|
|
time.sleep(self._pause_between_steps)
|
|
|
|
timed_out = False
|
|
total_timeout = Timeout(self.context.options.timeout_total)
|
|
|
|
while not self.debugger.is_finished:
|
|
## Check for timeouts.
|
|
breakpoint_timeout = Timeout(self.context.options.timeout_breakpoint)
|
|
while self.debugger.is_running and not timed_out:
|
|
# Check to see whether we've timed out while we're waiting.
|
|
if total_timeout.timed_out():
|
|
self.context.logger.error(
|
|
"Debugger session has been "
|
|
f"running for {total_timeout.elapsed}s, timeout reached!"
|
|
)
|
|
timed_out = True
|
|
if breakpoint_timeout.timed_out():
|
|
self.context.logger.error(
|
|
f"Debugger session has not "
|
|
f"hit a breakpoint for {breakpoint_timeout.elapsed}s, timeout "
|
|
"reached!"
|
|
)
|
|
timed_out = True
|
|
|
|
if timed_out or self.debugger.is_finished:
|
|
break
|
|
|
|
## Fetch frame information and breakpoint information from the debugger.
|
|
step_info: StepIR = self.debugger.get_stack_frames(self._step_index)
|
|
|
|
active_where_expects = get_active_where_expects(script, step_info)
|
|
|
|
watches = [
|
|
watch
|
|
for frame_idx, expects in active_where_expects.values()
|
|
for expect in expects
|
|
if (watch := expect.get_watched_expr())
|
|
]
|
|
self.debugger.collect_watches(step_info, watches)
|
|
|
|
# Our stepping behaviour is as follows:
|
|
# - If any !where matches the current stack frame, we step.
|
|
# - Otherwise, if any !where matches any non-current stack frame, we step out.
|
|
# - Otherwise, we continue.
|
|
if any(
|
|
frame_idx == 0 for frame_idx, expects in active_where_expects.values()
|
|
):
|
|
next_action = DebuggerAction.STEP_OVER
|
|
elif active_where_expects:
|
|
next_action = DebuggerAction.STEP_OUT
|
|
else:
|
|
next_action = DebuggerAction.CONTINUE
|
|
|
|
if step_info.current_frame:
|
|
self._step_index += 1
|
|
# Record the step in step_collection.
|
|
self.step_collection.new_step(self.context, step_info)
|
|
if self._step_index > self._max_steps:
|
|
next_action = DebuggerAction.EXIT
|
|
|
|
# If we have --trace enabled, report a short overview of this step.
|
|
self.context.logger.note(
|
|
f"Stopped at {step_info.current_function} {step_info.current_location.short_str()}, {len(active_where_expects)} !wheres on the stack, next_action={next_action}"
|
|
)
|
|
|
|
if next_action == DebuggerAction.EXIT:
|
|
break
|
|
elif next_action == DebuggerAction.STEP_OVER:
|
|
self.debugger.step_next()
|
|
elif next_action == DebuggerAction.STEP_OUT:
|
|
self.debugger.step_out()
|
|
else:
|
|
assert (
|
|
next_action == DebuggerAction.CONTINUE
|
|
), f"next_action has invalid value {next_action}"
|
|
self.debugger.go()
|
|
time.sleep(self._pause_between_steps)
|