Skip to content

[lldb] Implement basic support for reverse-continue #99736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 10, 2024

Conversation

rocallahan
Copy link
Contributor

This commit only adds support for the
SBProcess::ReverseContinue() API. A user-accessible command for this will follow in a later commit.

This feature depends on a gdbserver implementation (e.g. rr) providing support for the bc and bs packets. lldb-server does not support those packets, and there is no plan to change that. So, for testing purposes, lldbreverse.py wraps lldb-server with a Python implementation of very limited record-and-replay functionality for use by tests only.

The majority of this PR is test infrastructure (about 700 of the 950 lines added).

@rocallahan rocallahan requested a review from JDevlieghere as a code owner July 20, 2024 04:25
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be
notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write
permissions for the repository. In which case you can instead tag reviewers by
name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review
by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate
is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added the lldb label Jul 20, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 20, 2024

@llvm/pr-subscribers-lldb

Author: Robert O'Callahan (rocallahan)

Changes

This commit only adds support for the
SBProcess::ReverseContinue() API. A user-accessible command for this will follow in a later commit.

This feature depends on a gdbserver implementation (e.g. rr) providing support for the bc and bs packets. lldb-server does not support those packets, and there is no plan to change that. So, for testing purposes, lldbreverse.py wraps lldb-server with a Python implementation of very limited record-and-replay functionality for use by tests only.

The majority of this PR is test infrastructure (about 700 of the 950 lines added).


Patch is 56.36 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99736.diff

25 Files Affected:

  • (modified) lldb/include/lldb/API/SBProcess.h (+2)
  • (modified) lldb/include/lldb/Target/Process.h (+22-3)
  • (modified) lldb/include/lldb/Target/StopInfo.h (+3)
  • (modified) lldb/include/lldb/lldb-enumerations.h (+4)
  • (modified) lldb/packages/Python/lldbsuite/test/gdbclientutils.py (+3-2)
  • (added) lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py (+176)
  • (added) lldb/packages/Python/lldbsuite/test/lldbreverse.py (+415)
  • (modified) lldb/packages/Python/lldbsuite/test/lldbtest.py (+2)
  • (modified) lldb/source/API/SBProcess.cpp (+20)
  • (modified) lldb/source/API/SBThread.cpp (+2)
  • (modified) lldb/source/Interpreter/CommandInterpreter.cpp (+2-1)
  • (modified) lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp (+3)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp (+22)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h (+6)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp (+1)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp (+108-11)
  • (modified) lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h (+2)
  • (modified) lldb/source/Target/Process.cpp (+20-9)
  • (modified) lldb/source/Target/StopInfo.cpp (+29)
  • (modified) lldb/source/Target/Thread.cpp (+6-2)
  • (added) lldb/test/API/functionalities/reverse-execution/Makefile (+3)
  • (added) lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py (+79)
  • (added) lldb/test/API/functionalities/reverse-execution/main.c (+12)
  • (modified) lldb/tools/lldb-dap/JSONUtils.cpp (+3)
  • (modified) lldb/tools/lldb-dap/LLDBUtils.cpp (+1)
diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h
index 778be79583990..9b17bac0093e6 100644
--- a/lldb/include/lldb/API/SBProcess.h
+++ b/lldb/include/lldb/API/SBProcess.h
@@ -160,6 +160,8 @@ class LLDB_API SBProcess {
 
   lldb::SBError Continue();
 
+  lldb::SBError ReverseContinue();
+
   lldb::SBError Stop();
 
   lldb::SBError Kill();
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index c8475db8ae160..203d3484f3704 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -874,10 +874,10 @@ class Process : public std::enable_shared_from_this<Process>,
   /// \see Thread:Resume()
   /// \see Thread:Step()
   /// \see Thread:Suspend()
-  Status Resume();
+  Status Resume(lldb::RunDirection direction = lldb::eRunForward);
 
   /// Resume a process, and wait for it to stop.
-  Status ResumeSynchronous(Stream *stream);
+  Status ResumeSynchronous(Stream *stream, lldb::RunDirection direction = lldb::eRunForward);
 
   /// Halts a running process.
   ///
@@ -1136,6 +1136,22 @@ class Process : public std::enable_shared_from_this<Process>,
     return error;
   }
 
+  /// Like DoResume() but executes in reverse if supported.
+  ///
+  /// \return
+  ///     Returns \b true if the process successfully resumes using
+  ///     the thread run control actions, \b false otherwise.
+  ///
+  /// \see Thread:Resume()
+  /// \see Thread:Step()
+  /// \see Thread:Suspend()
+  virtual Status DoResumeReverse() {
+    Status error;
+    error.SetErrorStringWithFormatv(
+        "error: {0} does not support reverse execution of processes", GetPluginName());
+    return error;
+  }
+
   /// Called after resuming a process.
   ///
   /// Allow Process plug-ins to execute some code after resuming a process.
@@ -2367,6 +2383,8 @@ class Process : public std::enable_shared_from_this<Process>,
 
   bool IsRunning() const;
 
+  lldb::RunDirection GetLastRunDirection() { return m_last_run_direction; }
+
   DynamicCheckerFunctions *GetDynamicCheckers() {
     return m_dynamic_checkers_up.get();
   }
@@ -2861,7 +2879,7 @@ void PruneThreadPlans();
   ///
   /// \return
   ///     An Status object describing the success or failure of the resume.
-  Status PrivateResume();
+  Status PrivateResume(lldb::RunDirection direction = lldb::eRunForward);
 
   // Called internally
   void CompleteAttach();
@@ -3139,6 +3157,7 @@ void PruneThreadPlans();
                            // m_currently_handling_do_on_removals are true,
                            // Resume will only request a resume, using this
                            // flag to check.
+  lldb::RunDirection m_last_run_direction;
 
   /// This is set at the beginning of Process::Finalize() to stop functions
   /// from looking up or creating things during or after a finalize call.
diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h
index d1848fcbbbdb1..f49854275653e 100644
--- a/lldb/include/lldb/Target/StopInfo.h
+++ b/lldb/include/lldb/Target/StopInfo.h
@@ -138,6 +138,9 @@ class StopInfo : public std::enable_shared_from_this<StopInfo> {
   static lldb::StopInfoSP
   CreateStopReasonProcessorTrace(Thread &thread, const char *description);
 
+  static lldb::StopInfoSP
+  CreateStopReasonHistoryBoundary(Thread &thread, const char *description);
+
   static lldb::StopInfoSP CreateStopReasonFork(Thread &thread,
                                                lldb::pid_t child_pid,
                                                lldb::tid_t child_tid);
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 74ff458bf87de..509f1b76934d2 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -135,6 +135,9 @@ FLAGS_ENUM(LaunchFlags){
 /// Thread Run Modes.
 enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };
 
+/// Execution directions
+enum RunDirection { eRunForward, eRunReverse };
+
 /// Byte ordering definitions.
 enum ByteOrder {
   eByteOrderInvalid = 0,
@@ -253,6 +256,7 @@ enum StopReason {
   eStopReasonFork,
   eStopReasonVFork,
   eStopReasonVForkDone,
+  eStopReasonHistoryBoundary,
 };
 
 /// Command Return Status Types.
diff --git a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
index 1784487323ad6..732d617132068 100644
--- a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
+++ b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
@@ -510,8 +510,9 @@ def start(self):
         self._thread.start()
 
     def stop(self):
-        self._thread.join()
-        self._thread = None
+        if self._thread is not None:
+            self._thread.join()
+            self._thread = None
 
     def get_connect_address(self):
         return self._socket.get_connect_address()
diff --git a/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py b/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py
new file mode 100644
index 0000000000000..45018e7daa7df
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py
@@ -0,0 +1,176 @@
+import logging
+import os
+import os.path
+import random
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.gdbclientutils import *
+import lldbgdbserverutils
+from lldbsuite.support import seven
+
+
+class GDBProxyTestBase(TestBase):
+    """
+    Base class for gdbserver proxy tests.
+
+    This class will setup and start a mock GDB server for the test to use.
+    It pases through requests to a regular lldb-server/debugserver and
+    forwards replies back to the LLDB under test.
+    """
+
+    """The gdbserver that we implement."""
+    server = None
+    """The inner lldb-server/debugserver process that we proxy requests into."""
+    monitor_server = None
+    monitor_sock = None
+
+    server_socket_class = TCPServerSocket
+
+    DEFAULT_TIMEOUT = 20 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
+
+    _verbose_log_handler = None
+    _log_formatter = logging.Formatter(fmt="%(asctime)-15s %(levelname)-8s %(message)s")
+
+    def setUpBaseLogging(self):
+        self.logger = logging.getLogger(__name__)
+
+        if len(self.logger.handlers) > 0:
+            return  # We have set up this handler already
+
+        self.logger.propagate = False
+        self.logger.setLevel(logging.DEBUG)
+
+        # log all warnings to stderr
+        handler = logging.StreamHandler()
+        handler.setLevel(logging.WARNING)
+        handler.setFormatter(self._log_formatter)
+        self.logger.addHandler(handler)
+
+    def setUp(self):
+        TestBase.setUp(self)
+
+        self.setUpBaseLogging()
+
+        if self.isVerboseLoggingRequested():
+            # If requested, full logs go to a log file
+            log_file_name = self.getLogBasenameForCurrentTest() + "-proxy.log"
+            self._verbose_log_handler = logging.FileHandler(
+               log_file_name
+            )
+            self._verbose_log_handler.setFormatter(self._log_formatter)
+            self._verbose_log_handler.setLevel(logging.DEBUG)
+            self.logger.addHandler(self._verbose_log_handler)
+
+        self.port = self.get_next_port()
+        lldb_server_exe = lldbgdbserverutils.get_lldb_server_exe()
+        if lldb_server_exe is None:
+            self.debug_monitor_exe = lldbgdbserverutils.get_debugserver_exe()
+            self.assertTrue(self.debug_monitor_exe is not None)
+            self.debug_monitor_extra_args = []
+        else:
+            self.debug_monitor_exe = lldb_server_exe
+            self.debug_monitor_extra_args = ["gdbserver"]
+
+        self.server = MockGDBServer(self.server_socket_class())
+        self.server.responder = self
+
+    def tearDown(self):
+        # TestBase.tearDown will kill the process, but we need to kill it early
+        # so its client connection closes and we can stop the server before
+        # finally calling the base tearDown.
+        if self.process() is not None:
+            self.process().Kill()
+        self.server.stop()
+
+        self.logger.removeHandler(self._verbose_log_handler)
+        self._verbose_log_handler = None
+
+        TestBase.tearDown(self)
+
+    def isVerboseLoggingRequested(self):
+        # We will report our detailed logs if the user requested that the "gdb-remote" channel is
+        # logged.
+        return any(("gdb-remote" in channel) for channel in lldbtest_config.channels)
+
+    def connect(self, target):
+        """
+        Create a process by connecting to the mock GDB server.
+        """
+        self.prep_debug_monitor_and_inferior()
+        self.server.start()
+
+        listener = self.dbg.GetListener()
+        error = lldb.SBError()
+        process = target.ConnectRemote(
+            listener, self.server.get_connect_url(), "gdb-remote", error
+        )
+        self.assertTrue(error.Success(), error.description)
+        self.assertTrue(process, PROCESS_IS_VALID)
+        return process
+
+    def get_next_port(self):
+        return 12000 + random.randint(0, 3999)
+
+    def prep_debug_monitor_and_inferior(self):
+        inferior_exe_path = self.getBuildArtifact("a.out")
+        self.connect_to_debug_monitor([inferior_exe_path])
+        self.assertIsNotNone(self.monitor_server)
+        self.initial_handshake()
+
+    def initial_handshake(self):
+        self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
+        reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+        self.assertEqual(reply, "+")
+        self.monitor_server.send_packet(seven.bitcast_to_bytes("QStartNoAckMode"))
+        reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+        self.assertEqual(reply, "+")
+        reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+        self.assertEqual(reply, "OK")
+        self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
+        reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+        self.assertEqual(reply, "+")
+
+    def get_debug_monitor_command_line_args(self, connect_address, launch_args):
+        return self.debug_monitor_extra_args + ["--reverse-connect", connect_address] + launch_args
+
+    def launch_debug_monitor(self, launch_args):
+        family, type, proto, _, addr = socket.getaddrinfo(
+            "localhost", 0, proto=socket.IPPROTO_TCP
+        )[0]
+        sock = socket.socket(family, type, proto)
+        sock.settimeout(self.DEFAULT_TIMEOUT)
+        sock.bind(addr)
+        sock.listen(1)
+        addr = sock.getsockname()
+        connect_address = "[{}]:{}".format(*addr)
+
+        commandline_args = self.get_debug_monitor_command_line_args(
+            connect_address, launch_args
+        )
+
+        # Start the server.
+        self.logger.info(f"Spawning monitor {commandline_args}")
+        monitor_process = self.spawnSubprocess(
+            self.debug_monitor_exe, commandline_args, install_remote=False
+        )
+        self.assertIsNotNone(monitor_process)
+
+        self.monitor_sock = sock.accept()[0]
+        self.monitor_sock.settimeout(self.DEFAULT_TIMEOUT)
+        return monitor_process
+
+    def connect_to_debug_monitor(self, launch_args):
+        monitor_process = self.launch_debug_monitor(launch_args)
+        self.monitor_server = lldbgdbserverutils.Server(self.monitor_sock, monitor_process)
+
+    def respond(self, packet):
+        """Subclasses can override this to change how packets are handled."""
+        return self.pass_through(packet)
+
+    def pass_through(self, packet):
+        self.logger.info(f"Sending packet {packet}")
+        self.monitor_server.send_packet(seven.bitcast_to_bytes(packet))
+        reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
+        self.logger.info(f"Received reply {reply}")
+        return reply
diff --git a/lldb/packages/Python/lldbsuite/test/lldbreverse.py b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
new file mode 100644
index 0000000000000..a1fb396bd921f
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
@@ -0,0 +1,415 @@
+import os
+import os.path
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbgdbproxy import *
+import lldbgdbserverutils
+import re
+
+
+class ThreadSnapshot:
+    def __init__(self, thread_id, registers):
+        self.thread_id = thread_id
+        self.registers = registers
+
+
+class MemoryBlockSnapshot:
+    def __init__(self, address, data):
+        self.address = address
+        self.data = data
+
+
+class StateSnapshot:
+    def __init__(self, thread_snapshots, memory):
+        self.thread_snapshots = thread_snapshots
+        self.memory = memory
+        self.thread_id = None
+
+
+class RegisterInfo:
+    def __init__(self, lldb_index, bitsize, little_endian):
+        self.lldb_index = lldb_index
+        self.bitsize = bitsize
+        self.little_endian = little_endian
+
+
+BELOW_STACK_POINTER = 16384
+ABOVE_STACK_POINTER = 4096
+
+BLOCK_SIZE = 1024
+
+SOFTWARE_BREAKPOINTS = 0
+HARDWARE_BREAKPOINTS = 1
+WRITE_WATCHPOINTS = 2
+
+
+class ReverseTestBase(GDBProxyTestBase):
+    """
+    Base class for tests that need reverse execution.
+
+    This class uses a gdbserver proxy to add very limited reverse-
+    execution capability to lldb-server/debugserver for testing
+    purposes only.
+
+    To use this class, run the inferior forward until some stopping point.
+    Then call `start_recording()` and execute forward again until reaching
+    a software breakpoint; this class records the state before each execution executes.
+    At that point, the server will accept "bc" and "bs" packets to step
+    backwards through the state.
+    When executing during recording, we only allow single-step and continue without
+    delivering a signal, and only software breakpoint stops are allowed.
+
+    We assume that while recording is enabled, the only effects of instructions
+    are on general-purpose registers (read/written by the 'g' and 'G' packets)
+    and on memory bytes between [SP - BELOW_STACK_POINTER, SP + ABOVE_STACK_POINTER).
+    """
+
+    """
+    A list of StateSnapshots in time order.
+
+    There is one snapshot per single-stepped instruction,
+    representing the state before that instruction was
+    executed. The last snapshot in the list is the
+    snapshot before the last instruction was executed.
+    This is an undo log; we snapshot a superset of the state that may have
+    been changed by the instruction's execution.
+    """
+    snapshots = None
+    recording_enabled = False
+
+    breakpoints = None
+
+    pid = None
+
+    pc_register_info = None
+    sp_register_info = None
+    general_purpose_register_info = None
+
+    def __init__(self, *args, **kwargs):
+        GDBProxyTestBase.__init__(self, *args, **kwargs)
+        self.breakpoints = [set(), set(), set(), set(), set()]
+
+    def respond(self, packet):
+        if not packet:
+            raise ValueError("Invalid empty packet")
+        if self.is_command(packet, "qSupported", ":"):
+            reply = self.pass_through(packet)
+            return reply + ";ReverseStep+;ReverseContinue+"
+        if self.is_command(packet, "vCont", ";"):
+            if self.recording_enabled:
+                return self.continue_with_recording(packet)
+            snapshots = []
+        if packet[0] == "c" or packet[0] == "s" or packet[0] == "C" or packet[0] == "S":
+            raise ValueError("LLDB should not be sending old-style continuation packets")
+        if packet == "bc":
+            return self.reverse_continue()
+        if packet == "bs":
+            return self.reverse_step()
+        if packet == 'jThreadsInfo':
+            # Suppress this because it contains thread stop reasons which we might
+            # need to modify, and we don't want to have to implement that.
+            return ""
+        if packet[0] == "z" or packet[0] == "Z":
+            reply = self.pass_through(packet)
+            if reply == "OK":
+                self.update_breakpoints(packet)
+            return reply
+        return GDBProxyTestBase.respond(self, packet)
+
+    def start_recording(self):
+        self.recording_enabled = True
+        self.snapshots = []
+
+    def stop_recording(self):
+        """
+        Don't record when executing foward.
+
+        Reverse execution is still supported until the next forward continue.
+        """
+        self.recording_enabled = False
+
+    def is_command(self, packet, cmd, follow_token):
+        return packet == cmd or packet[0:len(cmd) + 1] == cmd + follow_token
+
+    def update_breakpoints(self, packet):
+        m = re.match("([zZ])([01234]),([0-9a-f]+),([0-9a-f]+)", packet)
+        if m is None:
+            raise ValueError("Invalid breakpoint packet: " + packet)
+        t = int(m.group(2))
+        addr = int(m.group(3), 16)
+        kind = int(m.group(4), 16)
+        if m.group(1) == 'Z':
+            self.breakpoints[t].add((addr, kind))
+        else:
+            self.breakpoints[t].discard((addr, kind))
+
+    def breakpoint_triggered_at(self, pc):
+        if any(addr == pc for addr, kind in self.breakpoints[SOFTWARE_BREAKPOINTS]):
+            return True
+        if any(addr == pc for addr, kind in self.breakpoints[HARDWARE_BREAKPOINTS]):
+            return True
+        return False
+
+    def watchpoint_triggered(self, new_value_block, current_contents):
+        """Returns the address or None."""
+        for watch_addr, kind in breakpoints[WRITE_WATCHPOINTS]:
+            for offset in range(0, kind):
+                addr = watch_addr + offset
+                if (addr >= new_value_block.address and
+                    addr < new_value_block.address + len(new_value_block.data)):
+                    index = addr - new_value_block.address
+                    if new_value_block.data[index*2:(index + 1)*2] != current_contents[index*2:(index + 1)*2]:
+                        return watch_addr
+        return None
+
+    def continue_with_recording(self, packet):
+        self.logger.debug("Continue with recording enabled")
+
+        step_packet = "vCont;s"
+        if packet == "vCont":
+            requested_step = False
+        else:
+            m = re.match("vCont;(c|s)(.*)", packet)
+            if m is None:
+                raise ValueError("Unsupported vCont packet: " + packet)
+            requested_step = m.group(1) == 's'
+            step_packet += m.group(2)
+
+        while True:
+            snapshot = self.capture_snapshot()
+            reply = self.pass_through(step_packet)
+            (stop_signal, stop_pairs) = self.parse_stop(reply)
+            if stop_signal != 5:
+                raise ValueError("Unexpected stop signal: " + reply)
+            is_swbreak = False
+            thread_id = None
+            for key, value in stop_pairs.items():
+                if key == "thread":
+                    thread_id = self.parse_thread_id(value)
+                    continue
+                if re.match('[0-9a-f]+', key):
+                    continue
+                if key == "swbreak" or (key == "reason" and value == "breakpoint"):
+                    is_swbreak = True
+                    continue
+                if key in ["name", "threads", "thread-pcs", "reason"]:
+                    continue
+                raise ValueError(f"Unknown stop key '{key}' in {reply}")
+            if is_swbreak:
+                self.logger.debug("Recording stopped")
+                return reply
+            if thread_id is None:
+                return ValueError("Expected thread ID: " + reply)
+            snapshot.thread_id = thread_id
+            self.snapshots.append(snapshot)
+            if requested_step:
+                self.logger.debug("Recording stopped for step")
+                return reply
+
+    def parse_stop(self, reply):
+        result = {}
+        if not reply:
+            raise ValueError("Invalid empty packet")
+        if reply[0] == "T" and len(reply) >= 3:
+            result = {k:v for k, v in self.parse_pairs(reply[3:])}
+            return (int(reply[1:3], 16), result)
+        raise "Unsupported stop reply: " + reply
+
+    def parse_pairs(self, text):
+        for pair in text.split(";"):
+            if not pair:
+                continu...
[truncated]

@rocallahan
Copy link
Contributor Author

Copy link
Member

@medismailben medismailben left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very interesting, I've left a few comments here and there but I've noticed you didn't check if this works both when the debugger is in synchronous and asynchronous in your tests. This is definitely something you want to exercise since it changes lldb's execution control behavior.

Also, I'm not sure what additional information does the History Boundary stop reason provides. Can it actually provide historical data to the user ? If so, I'm not sure the stop reason is the best place to put it. May be it would be worth adding a new subcommand to the trace top-level command ?

Thanks!

@walter-erquinigo
Copy link
Member

This is very exciting!

@rocallahan
Copy link
Contributor Author

I've noticed you didn't check if this works both when the debugger is in synchronous and asynchronous in your tests.

OK, I've added this.

I'm not sure what additional information does the History Boundary stop reason provides. Can it actually provide historical data to the user ?

When a user reverse-continues to the start of recorded time (typically the point where the target was initially execed), execution stops and we must report a reason. None of the existing reasons are appropriate so I created this new one. I've added an explanatory comment to its definition in lldb-enumerations.h.

@jimingham
Copy link
Collaborator

jimingham commented Jul 22, 2024 via email

@jimingham
Copy link
Collaborator

jimingham commented Jul 22, 2024 via email

@rocallahan
Copy link
Contributor Author

You might have to be careful in the case where someone runs a function call, stops at a breakpoint in the function call and decides to go backwards in the function execution (if that's possible).

That is not possible in rr. During user function calls it responds to reverse execution requests with an immediate stop.

@jimingham
Copy link
Collaborator

jimingham commented Jul 22, 2024 via email

@jimingham
Copy link
Collaborator

jimingham commented Jul 22, 2024 via email

@rocallahan rocallahan force-pushed the reverse-continue branch 2 times, most recently from 5ecca71 to 8d23087 Compare July 23, 2024 12:44
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only really reviewed the test code, but that looks (mostly) good to me.

Comment on lines -877 to +860
Status Resume();
Status Resume(lldb::RunDirection direction = lldb::eRunForward);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internal APIs, anything not in the lldb namespace can be changed. So this is ok. Though I personally would like to see a:

Status ReverseResume();

I am open to feedback from other here as my mind can easily be changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decision was to use a direction flag.

@jimingham
Copy link
Collaborator

jimingham commented Jul 23, 2024 via email

@jimingham
Copy link
Collaborator

jimingham commented Jul 23, 2024 via email

@jimingham
Copy link
Collaborator

BTW, another reason I want to focus here on direction and not on explicitly stating "reverse continue" and "actual resume" is that a lot of "forward" motion in a debug session with reverse debugging capability is actually going to be going "forward" but using the "reverse" machinery, right?

One pretty common workflow will be:

  1. set a breakpoint and run backwards to hit it,
  2. then run 'forwards' to figure out what happened there in the past
    • so for instance do a "forward next" to the next but still in the past source line
  3. realize that you stepped over a function you wanted to look at so you step 'backwards' from one point in the past to another (back to the function call you stepped over)

Most of the execution control logic should just know "forward" and "backwards", but then whether those are implemented by using the reverse debugging machinery or by allowing the program to run for realz will be know deeper in the execution control machinery - most likely governed by the thread plans.

@rocallahan
Copy link
Contributor Author

Most of the execution control logic should just know "forward" and "backwards", but then whether those are implemented by using the reverse debugging machinery or by allowing the program to run for realz will be know deeper in the execution control machinery - most likely governed by the thread plans.

Actually LLDB is (and should be) oblivious to whether the gdbserver is running the program "for realz" or not.

When using rr, we currently never run the program "for realz" while the user is in a debugging session. The workflow is: a) record a program run and save the recording to disk b) replay the run with the debugger attached. During b), both forward and reverse execution are "on rails", only observing what happened during recording. (Ok, ignoring user-requested function calls made through the debugger for now.)

UndoDB and gdb's reverse execution are a bit different. They do support the workflow you're thinking of, where the program is actually "live" while you explore history in the past. But the distinction between "replaying through history" and "runinng live" is never surfaced in the gdbserver protocol and from the debugger's point of view I don't think it should matter.

@JDevlieghere JDevlieghere merged commit d5e1de6 into llvm:main Oct 10, 2024
6 checks passed
Copy link

@rocallahan Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

@JDevlieghere
Copy link
Member

SetErrorStringWithFormatv was renamed to FromErrorStringWithFormatv. Fixed in b77fdf5.

@augusto2112
Copy link
Contributor

Looks like TestReverseContinueBreakpoints.py is failing on the bots (https://green.lab.llvm.org/job/llvm.org/view/LLDB/job/as-lldb-cmake/13228/console)

@rocallahan
Copy link
Contributor Author

I don't know what your normal protocol is but maybe you should revert it while I debug the failure?

The last successful CI run in this PR was from September 25.

@augusto2112
Copy link
Contributor

I'll revert it!

augusto2112 added a commit that referenced this pull request Oct 10, 2024
@augusto2112
Copy link
Contributor

I reverted both this commit and also b77fdf5, so you'll need to add that change to your commit before merging again.

jasonmolenda pushed a commit that referenced this pull request Oct 10, 2024
This commit only adds support for the
`SBProcess::ReverseContinue()` API. A user-accessible command for this
will follow in a later commit.

This feature depends on a gdbserver implementation (e.g. `rr`) providing
support for the `bc` and `bs` packets. `lldb-server` does not support
those packets, and there is no plan to change that. So, for testing
purposes, `lldbreverse.py` wraps `lldb-server` with a Python
implementation of *very limited* record-and-replay functionality for use
by *tests only*.

The majority of this PR is test infrastructure (about 700 of the 950
lines added).
jasonmolenda added a commit that referenced this pull request Oct 10, 2024
Reverting this again; I added a commit which added @skipIfDarwin
markers to the TestReverseContinueBreakpoints.py and
TestReverseContinueNotSupported.py API tests, which use lldb-server
in gdbserver mode which does not work on Darwin.  But the aarch64 ubuntu
bot reported a failure on TestReverseContinueBreakpoints.py,
https://lab.llvm.org/buildbot/#/builders/59/builds/6397

  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 63, in test_reverse_continue_skip_breakpoint
    self.reverse_continue_skip_breakpoint_internal(async_mode=False)
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 81, in reverse_continue_skip_breakpoint_internal
    self.expect(
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 2372, in expect
    self.runCmd(
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1002, in runCmd
    self.assertTrue(self.res.Succeeded(), msg + output)
AssertionError: False is not true : Process should be stopped due to history boundary
Error output:
error: Process must be launched.

This reverts commit 4f29756.
@jasonmolenda
Copy link
Collaborator

jasonmolenda commented Oct 10, 2024

I tried re-applying this change and Jonas' fae7d68 to fix the Formatv call, and added @skipIfDarwin markers to the TestReverseContinueBreakpoints.py and TestReverseContinueNotSupported.py API tests because lldb-server is not supported in gdbserver mode on Darwin systems (c686eeb).

But now it is faililng on an aarch64 Ubuntu bot,
https://lab.llvm.org/buildbot/#/builders/59/builds/6397

      File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 63, in test_reverse_continue_skip_breakpoint
        self.reverse_continue_skip_breakpoint_internal(async_mode=False)
      File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 81, in reverse_continue_skip_breakpoint_internal
        self.expect(
      File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 2372, in expect
        self.runCmd(
      File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1002, in runCmd
        self.assertTrue(self.res.Succeeded(), msg + output)
    AssertionError: False is not true : Process should be stopped due to history boundary
    Error output:
    error: Process must be launched.

so I reverted all three patches again.

@DavidSpickett
Copy link
Collaborator

Failed on Arm and AArch64:

FAIL: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_reverse_continue_skip_breakpoint (TestReverseContinueBreakpoints.TestReverseContinueBreakpoints)
lldb-server exiting...
error: stopped due to an error evaluating condition of breakpoint 3.1: "false_condition"
Couldn't execute expression:
error: Couldn't materialize: couldn't get the value of false_condition: extracting data from value failed
error: errored out in DoExecute, couldn't PrepareToExecuteJITExpression

I'm looking at it now.

@DavidSpickett
Copy link
Collaborator

This is something to do with evaluating false_condition.

With a normal session we see these libraries:

(lldb) image list
[  0] 371298DF 0x0000aaaaaaaaa000 /home/david.spickett/build-llvm-aarch64/lldb-test-build.noindex/functionalities/reverse-execution/TestReverseContinueBreakpoints.test_reverse_continue_skip_breakpoint/a.out
[  1] C38E00DD-D72E-215A-9DF1-C295FE7D461A-2EF398FF 0x0000fffff7fc2000 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
      /usr/lib/debug/.build-id/c3/8e00ddd72e215a9df1c295fe7d461a2ef398ff.debug
[  2] 8A3F013D-F292-A2BC-FE56-490D2244E9A8-A6162A47 0x0000fffff7ffb000 [vdso] (0x0000fffff7ffb000)
[  3] A012B2BB-7711-0E84-B266-CD7425B50E57-427ABB02 0x0000fffff7d90000 /lib/aarch64-linux-gnu/libstdc++.so.6
[  4] 6EAA5DD8-8BB4-2CB9-93DA-1B6C497683D1-71334ED1 0x0000fffff7cf0000 /lib/aarch64-linux-gnu/libm.so.6
      /usr/lib/debug/.build-id/6e/aa5dd88bb42cb993da1b6c497683d171334ed1.debug
[  5] AD8704B8-637D-63A8-F746-C5E0CDEC8708-40569A81 0x0000fffff7cc0000 /lib/aarch64-linux-gnu/libgcc_s.so.1
[  6] AA6E122F-A39A-E02D-412A-FB49D75E3328-1FCD2805 0x0000fffff7b10000 /lib/aarch64-linux-gnu/libc.so.6
      /usr/lib/debug/.build-id/aa/6e122fa39ae02d412afb49d75e33281fcd2805.debug

And when we look up mmap we find:

(lldb) image lookup -n mmap
1 match found in /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1:
        Address: ld-linux-aarch64.so.1[0x000000000001d790] (ld-linux-aarch64.so.1.PT_LOAD[0]..text + 113488)
        Summary: ld-linux-aarch64.so.1`__mmap64 at mmap64.c:50:6
1 match found in /lib/aarch64-linux-gnu/libc.so.6:
        Address: libc.so.6[0x00000000000e1db0] (libc.so.6.PT_LOAD[0]..text + 765296)
        Summary: libc.so.6`__GI___mmap64 at mmap64.c:50:6

And use the first one.

With the proxy server we see:

(lldb) image list
[  0] 371298DF 0x0000aaaaaaaaa000 /home/david.spickett/build-llvm-aarch64/lldb-test-build.noindex/functionalities/reverse-execution/TestReverseContinueBreakpoints.test_reverse_continue_skip_breakpoint/a.out
[  1] 8A3F013D-F292-A2BC-FE56-490D2244E9A8-A6162A47 0x0000fffff7ffb000 [vdso] (0x0000fffff7ffb000)
[  2] C38E00DD-D72E-215A-9DF1-C295FE7D461A-2EF398FF 0x0000fffff7fc2000 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
[  3] A012B2BB-7711-0E84-B266-CD7425B50E57-427ABB02 0x0000fffff7d90000 /lib/aarch64-linux-gnu/libstdc++.so.6
[  4] 6EAA5DD8-8BB4-2CB9-93DA-1B6C497683D1-71334ED1 0x0000fffff7cf0000 /lib/aarch64-linux-gnu/libm.so.6
[  5] AD8704B8-637D-63A8-F746-C5E0CDEC8708-40569A81 0x0000fffff7cc0000 /lib/aarch64-linux-gnu/libgcc_s.so.1
[  6] AA6E122F-A39A-E02D-412A-FB49D75E3328-1FCD2805 0x0000fffff7b10000 /lib/aarch64-linux-gnu/libc.so.6

And we find:

runCmd: image lookup -n mmap

output: 1 match found in /lib/aarch64-linux-gnu/libc.so.6:
        Address: libc.so.6[0x00000000000e1db0] (libc.so.6.PT_LOAD[0]..text + 765296)
        Summary: libc.so.6`__mmap

I haven't got to exactly where it crashes but I see the debug server exit, so I suspect we're not meant to call this __mmap. Apparently this is usually an alias to __mmap64 but I wonder if we're breaking that alias by using it this way, unlike C programs using glibc normally.

Anyway, that aside the problem is likely that we don't find the debug info files when using the proxy. I'm trying to find out why, I suspect some aspect of the real server isn't being passed on correctly.

@DavidSpickett
Copy link
Collaborator

So on x86, either false_condition doesn't require JIT, or it does and the mmap found is fine. I tried changing it to 0 or false, and while this will fall back to an interpreter, the lldb-server appears to have already died by the time that finishes.

@DavidSpickett
Copy link
Collaborator

The only info I can get out of the call to __mmap is that it's frame 0 is at 0xffffffff aka "the process died for some reason". I'm thinking that something is preventing us finding the debug info files because this is a "remote" not a local server, haven't found what yet.

We can reland this as x86 only, but give me a few days to see if I can figure this out.

if self.is_command(packet, "vCont", ";"):
if self.recording_enabled:
return self.continue_with_recording(packet)
snapshots = []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like at best a nop (this is a local variable not the self.snapshots), or a bug.

@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Oct 11, 2024

The problem is not to do with the debug symbols, or it being a "properly remote" connection due to the proxy and that messing up search paths.

It's because evaluating an expression using JIT requires us to send a vCont and something goes wrong there, it appears to be missing completely.

I know that if I stick while (1) {} at the end of the main, the test hangs forever. Which tells me that the expression calling
mmap somehow ends up running to the end of main. Perhaps the PC gets reset? Or we don't exec the expression at all.

Anyway, here is the log from a working non-proxied session, when I do p false_condition:

lldb             <  37> send packet: $QSaveRegisterState;thread:3486f4;#bd
lldb             <   5> read packet: $3#33
lldb             <  38> send packet: $P0=0000000000000000;thread:3486f4;#54
lldb             <   6> read packet: $OK#9a
<...setup more argument registers...>
lldb             <  21> send packet: $Z0,aaaaaaaaa600,4#15
lldb             <   6> read packet: $OK#9a
<...placed a breakpoint for the return address which will be _start from the c library...>
lldb             <  22> send packet: $p20;thread:3486f4;#69
lldb             <  20> read packet: $90f7fdf7ffff0000#c5
<...read a register for some reason...>
b-remote.async>  <  22> send packet: $vCont;c:p3486f4.-1#4d
<...continue from the start of the expression wrapper function that we JIT'd...>
intern-state     <  42> send packet: $QRestoreRegisterState:3;thread:3486f4;#7f
<...restore the original state...>
intern-state     <   6> read packet: $OK#9a

This is the proxy's log for the same expression:

2024-10-11 16:01:36,939 INFO     Sending packet _M1000,rwx
2024-10-11 16:01:36,939 INFO     Received reply
2024-10-11 16:01:36,939 INFO     Sending packet QSaveRegisterState;thread:348766;
2024-10-11 16:01:36,940 INFO     Received reply 1
2024-10-11 16:01:36,940 INFO     Sending packet P0=0000000000000000;thread:348766;
2024-10-11 16:01:36,940 INFO     Received reply OK
2024-10-11 16:01:36,940 INFO     Sending packet P1=0010000000000000;thread:348766;
2024-10-11 16:01:36,940 INFO     Received reply OK
2024-10-11 16:01:36,941 INFO     Sending packet P2=0700000000000000;thread:348766;
2024-10-11 16:01:36,941 INFO     Received reply OK
2024-10-11 16:01:36,941 INFO     Sending packet P3=2200000000000000;thread:348766;
2024-10-11 16:01:36,941 INFO     Received reply OK
2024-10-11 16:01:36,941 INFO     Sending packet P4=ffffffffffffffff;thread:348766;
2024-10-11 16:01:36,941 INFO     Received reply OK
2024-10-11 16:01:36,941 INFO     Sending packet P5=0000000000000000;thread:348766;
2024-10-11 16:01:36,941 INFO     Received reply OK
2024-10-11 16:01:36,942 INFO     Sending packet P1e=00a6aaaaaaaa0000;thread:348766;
2024-10-11 16:01:36,942 INFO     Received reply OK
2024-10-11 16:01:36,942 INFO     Sending packet P1f=30edffffffff0000;thread:348766;
2024-10-11 16:01:36,942 INFO     Received reply OK
2024-10-11 16:01:36,942 INFO     Sending packet P20=b01dbff7ffff0000;thread:348766;
2024-10-11 16:01:36,942 INFO     Received reply OK
2024-10-11 16:01:36,942 INFO     Sending packet Z0,aaaaaaaaa600,4
2024-10-11 16:01:36,943 INFO     Received reply OK
2024-10-11 16:01:36,943 INFO     Sending packet p0;thread:348766;
2024-10-11 16:01:36,943 INFO     Received reply 0000000000000000
2024-10-11 16:01:36,943 INFO     Sending packet p20;thread:348766;
2024-10-11 16:01:36,943 INFO     Received reply b01dbff7ffff0000
2024-10-11 16:01:36,943 INFO     Sending packet QRestoreRegisterState:1;thread:348766;
2024-10-11 16:01:36,943 INFO     Received reply OK
2024-10-11 16:01:36,944 INFO     Sending packet p20;thread:348766;
2024-10-11 16:01:36,944 INFO     Received reply 4ca7aaaaaaaa0000
2024-10-11 16:01:36,944 INFO     Sending packet z0,aaaaaaaaa74c,4
2024-10-11 16:01:36,944 INFO     Received reply OK
2024-10-11 16:01:36,944 INFO     Sending packet vCont;s:p348766.348766

It's as if there is a vCont missing between the QSaveRegisterState and QRestoreRegisterState.

Is it possible that because this trigger breakpoint is hit when running in reverse, we're also trying to execute the expression "in reverse"? Getting very confused, and deciding that not doing anything is the best solution.

Feels like the proxy's handling of vCont isn't at fault so much us running in reverse but then needing to forward execute to evaluate the expression. Because I'm able to evaluate the expression immediately prior to the status = process.Continue(lldb.eRunReverse) line.

I'm testing this on an AArch64 machine but I think you might be able to trigger this on X86 as well by making the breakpoint condition a function call instead of a variable.

@DavidSpickett
Copy link
Collaborator

Also X86 may support the memory allocation packets and not need to call mmap to get JIT memory. So if making it a function call doesn't reproduce it, try hacking out support for _M.

@rocallahan
Copy link
Contributor Author

I have created a new PR #112079 to continue work on this.
Is there a way to run the full post-merge CI test suite against a Github PR?

@rocallahan
Copy link
Contributor Author

added @skipIfDarwin markers to the TestReverseContinueBreakpoints.py and TestReverseContinueNotSupported.py API tests because lldb-server is not supported in gdbserver mode on Darwin systems

So there is no LLDB-based daemon that implements the gdbserver protocol on Darwin systems?

@medismailben
Copy link
Member

Answered in your new PR.

DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
This commit only adds support for the
`SBProcess::ReverseContinue()` API. A user-accessible command for this
will follow in a later commit.

This feature depends on a gdbserver implementation (e.g. `rr`) providing
support for the `bc` and `bs` packets. `lldb-server` does not support
those packets, and there is no plan to change that. So, for testing
purposes, `lldbreverse.py` wraps `lldb-server` with a Python
implementation of *very limited* record-and-replay functionality for use
by *tests only*.

The majority of this PR is test infrastructure (about 700 of the 950
lines added).
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
This commit only adds support for the
`SBProcess::ReverseContinue()` API. A user-accessible command for this
will follow in a later commit.

This feature depends on a gdbserver implementation (e.g. `rr`) providing
support for the `bc` and `bs` packets. `lldb-server` does not support
those packets, and there is no plan to change that. So, for testing
purposes, `lldbreverse.py` wraps `lldb-server` with a Python
implementation of *very limited* record-and-replay functionality for use
by *tests only*.

The majority of this PR is test infrastructure (about 700 of the 950
lines added).
DanielCChen pushed a commit to DanielCChen/llvm-project that referenced this pull request Oct 16, 2024
…)"

Reverting this again; I added a commit which added @skipIfDarwin
markers to the TestReverseContinueBreakpoints.py and
TestReverseContinueNotSupported.py API tests, which use lldb-server
in gdbserver mode which does not work on Darwin.  But the aarch64 ubuntu
bot reported a failure on TestReverseContinueBreakpoints.py,
https://lab.llvm.org/buildbot/#/builders/59/builds/6397

  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 63, in test_reverse_continue_skip_breakpoint
    self.reverse_continue_skip_breakpoint_internal(async_mode=False)
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/functionalities/reverse-execution/TestReverseContinueBreakpoints.py", line 81, in reverse_continue_skip_breakpoint_internal
    self.expect(
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 2372, in expect
    self.runCmd(
  File "/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/packages/Python/lldbsuite/test/lldbtest.py", line 1002, in runCmd
    self.assertTrue(self.res.Succeeded(), msg + output)
AssertionError: False is not true : Process should be stopped due to history boundary
Error output:
error: Process must be launched.

This reverts commit 4f29756.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.