Revert "[lldb] Implement basic support for reverse-continue (#99736)"

This reverts commit d5e1de6da9.
This commit is contained in:
Augusto Noronha
2024-10-10 15:01:20 -07:00
parent f02252e1fd
commit 2ff4c25b7e
32 changed files with 44 additions and 978 deletions

View File

@@ -159,7 +159,6 @@ public:
lldb::SBError Destroy();
lldb::SBError Continue();
lldb::SBError Continue(RunDirection direction);
lldb::SBError Stop();

View File

@@ -857,10 +857,10 @@ public:
/// \see Thread:Resume()
/// \see Thread:Step()
/// \see Thread:Suspend()
Status Resume(lldb::RunDirection direction = lldb::eRunForward);
Status Resume();
/// Resume a process, and wait for it to stop.
Status ResumeSynchronous(Stream *stream, lldb::RunDirection direction = lldb::eRunForward);
Status ResumeSynchronous(Stream *stream);
/// Halts a running process.
///
@@ -1104,14 +1104,9 @@ public:
/// \see Thread:Resume()
/// \see Thread:Step()
/// \see Thread:Suspend()
virtual Status DoResume(lldb::RunDirection direction) {
if (direction == lldb::RunDirection::eRunForward) {
return Status::FromErrorStringWithFormatv(
"error: {0} does not support resuming processes", GetPluginName());
} else {
return Status::FromErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
}
virtual Status DoResume() {
return Status::FromErrorStringWithFormatv(
"error: {0} does not support resuming processes", GetPluginName());
}
/// Called after resuming a process.
@@ -2337,8 +2332,6 @@ public:
bool IsRunning() const;
lldb::RunDirection GetLastRunDirection() { return m_last_run_direction; }
DynamicCheckerFunctions *GetDynamicCheckers() {
return m_dynamic_checkers_up.get();
}
@@ -2858,7 +2851,7 @@ protected:
///
/// \return
/// An Status object describing the success or failure of the resume.
Status PrivateResume(lldb::RunDirection direction = lldb::eRunForward);
Status PrivateResume();
// Called internally
void CompleteAttach();
@@ -3134,8 +3127,6 @@ protected:
// m_currently_handling_do_on_removals are true,
// Resume will only request a resume, using this
// flag to check.
// The direction of execution from the last time this process was resumed.
lldb::RunDirection m_last_run_direction;
lldb::tid_t m_interrupt_tid; /// The tid of the thread that issued the async
/// interrupt, used by thread plan timeout. It

View File

@@ -142,12 +142,6 @@ public:
static lldb::StopInfoSP
CreateStopReasonProcessorTrace(Thread &thread, const char *description);
// This creates a StopInfo indicating that execution stopped because
// it was replaying some recorded execution history, and execution reached
// the end of that recorded history.
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);

View File

@@ -135,9 +135,6 @@ FLAGS_ENUM(LaunchFlags){
/// Thread Run Modes.
enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };
/// Execution directions
enum RunDirection { eRunForward, eRunReverse };
/// Byte ordering definitions.
enum ByteOrder {
eByteOrderInvalid = 0,
@@ -257,9 +254,6 @@ enum StopReason {
eStopReasonVFork,
eStopReasonVForkDone,
eStopReasonInterrupt, ///< Thread requested interrupt
// Indicates that execution stopped because the debugger backend relies
// on recorded data and we reached the end of that data.
eStopReasonHistoryBoundary,
};
/// Command Return Status Types.

View File

@@ -510,9 +510,8 @@ class MockGDBServer:
self._thread.start()
def stop(self):
if self._thread is not None:
self._thread.join()
self._thread = None
self._thread.join()
self._thread = None
def get_connect_address(self):
return self._socket.get_connect_address()

View File

@@ -1,175 +0,0 @@
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)
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

View File

@@ -1,418 +0,0 @@
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 packet == self.server.PACKET_INTERRUPT:
# Don't send a response. We'll just run to completion.
return []
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:
continue
m = re.match("([^:]+):(.*)", pair)
if m is None:
raise ValueError("Invalid pair text: " + text)
yield (m.group(1), m.group(2))
def capture_snapshot(self):
"""Snapshot all threads and their stack memories."""
self.ensure_register_info()
current_thread = self.get_current_thread()
thread_snapshots = []
memory = []
for thread_id in self.get_thread_list():
registers = {}
for index in sorted(self.general_purpose_register_info.keys()):
reply = self.pass_through(f"p{index:x};thread:{thread_id:x};")
if reply == "" or reply[0] == 'E':
raise ValueError("Can't read register")
registers[index] = reply
thread_snapshot = ThreadSnapshot(thread_id, registers)
thread_sp = self.get_register(self.sp_register_info, thread_snapshot.registers)
memory += self.read_memory(thread_sp - BELOW_STACK_POINTER, thread_sp + ABOVE_STACK_POINTER)
thread_snapshots.append(thread_snapshot)
self.set_current_thread(current_thread)
return StateSnapshot(thread_snapshots, memory)
def restore_snapshot(self, snapshot):
"""
Restore the snapshot during reverse execution.
If this triggers a breakpoint or watchpoint, return the stop reply,
otherwise None.
"""
current_thread = self.get_current_thread()
stop_reasons = []
for thread_snapshot in snapshot.thread_snapshots:
thread_id = thread_snapshot.thread_id
for lldb_index in sorted(thread_snapshot.registers.keys()):
data = thread_snapshot.registers[lldb_index]
reply = self.pass_through(f"P{lldb_index:x}={data};thread:{thread_id:x};")
if reply != "OK":
raise ValueError("Can't restore thread register")
if thread_id == snapshot.thread_id:
new_pc = self.get_register(self.pc_register_info, thread_snapshot.registers)
if self.breakpoint_triggered_at(new_pc):
stop_reasons.append([("reason", "breakpoint")])
self.set_current_thread(current_thread)
for block in snapshot.memory:
current_memory = self.pass_through(f"m{block.address:x},{(len(block.data)/2):x}")
if not current_memory or current_memory[0] == 'E':
raise ValueError("Can't read back memory")
reply = self.pass_through(f"M{block.address:x},{len(block.data)/2:x}:" + block.data)
if reply != "OK":
raise ValueError("Can't restore memory")
watch_addr = self.watchpoint_triggered(block, current_memory[1:])
if watch_addr is not None:
stop_reasons.append([("reason", "watchpoint"), ("watch", f"{watch_addr:x}")])
if stop_reasons:
pairs = ";".join(f"{key}:{value}" for key, value in stop_reasons[0])
return f"T05thread:{self.pid:x}.{snapshot.thread_id:x};{pairs};"
return None
def reverse_step(self):
if not self.snapshots:
self.logger.debug("Reverse-step at history boundary")
return self.history_boundary_reply(self.get_current_thread())
self.logger.debug("Reverse-step started")
snapshot = self.snapshots.pop()
stop_reply = self.restore_snapshot(snapshot)
self.set_current_thread(snapshot.thread_id)
self.logger.debug("Reverse-step stopped")
if stop_reply is None:
return self.singlestep_stop_reply(snapshot.thread_id)
return stop_reply
def reverse_continue(self):
self.logger.debug("Reverse-continue started")
thread_id = None
while self.snapshots:
snapshot = self.snapshots.pop()
stop_reply = self.restore_snapshot(snapshot)
thread_id = snapshot.thread_id
if stop_reply is not None:
self.set_current_thread(thread_id)
self.logger.debug("Reverse-continue stopped")
return stop_reply
if thread_id is None:
thread_id = self.get_current_thread()
else:
self.set_current_thread(snapshot.thread_id)
self.logger.debug("Reverse-continue stopped at history boundary")
return self.history_boundary_reply(thread_id)
def get_current_thread(self):
reply = self.pass_through("qC")
return self.parse_thread_id(reply[2:])
def parse_thread_id(self, thread_id):
m = re.match("(p([0-9a-f]+)[.])?([0-9a-f]+)$", thread_id)
if m is None:
raise ValueError("Invalid thread ID: " + thread_id)
if self.pid is None:
self.pid = int(m.group(2), 16)
return int(m.group(3), 16)
def history_boundary_reply(self, thread_id):
return f"T00thread:{self.pid:x}.{thread_id:x};replaylog:begin;"
def singlestep_stop_reply(self, thread_id):
return f"T05thread:{self.pid:x}.{thread_id:x};"
def set_current_thread(self, thread_id):
"""
Set current thread in inner gdbserver.
"""
if thread_id >= 0:
self.pass_through(f"Hg{self.pid:x}.{thread_id:x}")
self.pass_through(f"Hc{self.pid:x}.{thread_id:x}")
else:
self.pass_through(f"Hc-1.-1")
self.pass_through(f"Hg-1.-1")
def get_register(self, register_info, registers):
if register_info.bitsize % 8 != 0:
raise ValueError("Register size must be a multiple of 8 bits")
if register_info.lldb_index not in registers:
raise ValueError("Register value not captured")
data = registers[register_info.lldb_index]
num_bytes = register_info.bitsize//8
bytes = []
for i in range(0, num_bytes):
bytes.append(int(data[i*2:(i + 1)*2], 16))
if register_info.little_endian:
bytes.reverse()
result = 0
for byte in bytes:
result = (result << 8) + byte
return result
def read_memory(self, start_addr, end_addr):
"""
Read a region of memory from the target.
Some of the addresses may extend into invalid virtual memory;
skip those areas.
Return a list of blocks containing the valid area(s) in the
requested range.
"""
regions = []
start_addr = start_addr & (BLOCK_SIZE - 1)
end_addr = (end_addr + BLOCK_SIZE - 1) & (BLOCK_SIZE - 1)
for addr in range(start_addr, end_addr, BLOCK_SIZE):
reply = self.pass_through(f"m{addr:x},{(BLOCK_SIZE - 1):x}")
if reply and reply[0] != 'E':
block = MemoryBlockSnapshot(addr, reply[1:])
regions.append(block)
return regions
def ensure_register_info(self):
if self.general_purpose_register_info is not None:
return
reply = self.pass_through("qHostInfo")
little_endian = any(kv == ("endian", "little") for kv in self.parse_pairs(reply))
self.general_purpose_register_info = {}
lldb_index = 0
while True:
reply = self.pass_through(f"qRegisterInfo{lldb_index:x}")
if not reply or reply[0] == 'E':
break
info = {k:v for k, v in self.parse_pairs(reply)}
reg_info = RegisterInfo(lldb_index, int(info["bitsize"]), little_endian)
if info["set"] == "General Purpose Registers" and not "container-regs" in info:
self.general_purpose_register_info[lldb_index] = reg_info
if "generic" in info:
if info["generic"] == "pc":
self.pc_register_info = reg_info
elif info["generic"] == "sp":
self.sp_register_info = reg_info
lldb_index += 1
if self.pc_register_info is None or self.sp_register_info is None:
raise ValueError("Can't find generic pc or sp register")
def get_thread_list(self):
threads = []
reply = self.pass_through("qfThreadInfo")
while True:
if not reply:
raise ValueError("Missing reply packet")
if reply[0] == 'm':
for id in reply[1:].split(","):
threads.append(self.parse_thread_id(id))
elif reply[0] == 'l':
return threads
reply = self.pass_through("qsThreadInfo")

View File

@@ -143,8 +143,6 @@ STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in"
STOPPED_DUE_TO_WATCHPOINT = "Process should be stopped due to watchpoint"
STOPPED_DUE_TO_HISTORY_BOUNDARY = "Process should be stopped due to history boundary"
DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly"
VALID_BREAKPOINT = "Got a valid breakpoint"

View File

@@ -564,10 +564,6 @@ uint32_t SBProcess::GetAddressByteSize() const {
}
SBError SBProcess::Continue() {
return Continue(RunDirection::eRunForward);
}
SBError SBProcess::Continue(RunDirection direction) {
LLDB_INSTRUMENT_VA(this);
SBError sb_error;
@@ -578,9 +574,9 @@ SBError SBProcess::Continue(RunDirection direction) {
process_sp->GetTarget().GetAPIMutex());
if (process_sp->GetTarget().GetDebugger().GetAsyncExecution())
sb_error.ref() = process_sp->Resume(direction);
sb_error.ref() = process_sp->Resume();
else
sb_error.ref() = process_sp->ResumeSynchronous(nullptr, direction);
sb_error.ref() = process_sp->ResumeSynchronous(nullptr);
} else
sb_error = Status::FromErrorString("SBProcess is invalid");

View File

@@ -172,7 +172,6 @@ size_t SBThread::GetStopReasonDataCount() {
case eStopReasonInstrumentation:
case eStopReasonProcessorTrace:
case eStopReasonVForkDone:
case eStopReasonHistoryBoundary:
// There is no data for these stop reasons.
return 0;
@@ -234,7 +233,6 @@ uint64_t SBThread::GetStopReasonDataAtIndex(uint32_t idx) {
case eStopReasonInstrumentation:
case eStopReasonProcessorTrace:
case eStopReasonVForkDone:
case eStopReasonHistoryBoundary:
// There is no data for these stop reasons.
return 0;

View File

@@ -2553,8 +2553,7 @@ bool CommandInterpreter::DidProcessStopAbnormally() const {
const StopReason reason = stop_info->GetStopReason();
if (reason == eStopReasonException ||
reason == eStopReasonInstrumentation ||
reason == eStopReasonProcessorTrace || reason == eStopReasonInterrupt ||
reason == eStopReasonHistoryBoundary)
reason == eStopReasonProcessorTrace || reason == eStopReasonInterrupt)
return true;
if (reason == eStopReasonSignal) {

View File

@@ -82,9 +82,6 @@ void LogThreadStopInfo(Log &log, const ThreadStopInfo &stop_info,
case eStopReasonProcessorTrace:
log.Printf("%s: %s processor trace", __FUNCTION__, header);
return;
case eStopReasonHistoryBoundary:
log.Printf("%s: %s history boundary", __FUNCTION__, header);
return;
default:
log.Printf("%s: %s invalid stop reason %" PRIu32, __FUNCTION__, header,
static_cast<uint32_t>(stop_info.reason));

View File

@@ -402,16 +402,9 @@ lldb_private::DynamicLoader *ProcessKDP::GetDynamicLoader() {
Status ProcessKDP::WillResume() { return Status(); }
Status ProcessKDP::DoResume(RunDirection direction) {
Status ProcessKDP::DoResume() {
Status error;
Log *log = GetLog(KDPLog::Process);
if (direction == RunDirection::eRunReverse) {
error.SetErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
return error;
}
// Only start the async thread if we try to do any process control
if (!m_async_thread.IsJoinable())
StartAsyncThread();

View File

@@ -90,7 +90,7 @@ public:
// Process Control
lldb_private::Status WillResume() override;
lldb_private::Status DoResume(lldb::RunDirection direction) override;
lldb_private::Status DoResume() override;
lldb_private::Status DoHalt(bool &caused_stop) override;

View File

@@ -204,17 +204,11 @@ ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid,
return error;
}
Status ProcessWindows::DoResume(RunDirection direction) {
Status ProcessWindows::DoResume() {
Log *log = GetLog(WindowsLog::Process);
llvm::sys::ScopedLock lock(m_mutex);
Status error;
if (direction == RunDirection::eRunReverse) {
error.SetErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
return error;
}
StateType private_state = GetPrivateState();
if (private_state == eStateStopped || private_state == eStateCrashed) {
LLDB_LOG(log, "process {0} is in state {1}. Resuming...",

View File

@@ -52,7 +52,7 @@ public:
Status DoAttachToProcessWithID(
lldb::pid_t pid,
const lldb_private::ProcessAttachInfo &attach_info) override;
Status DoResume(lldb::RunDirection direction) override;
Status DoResume() override;
Status DoDestroy() override;
Status DoHalt(bool &caused_stop) override;

View File

@@ -199,20 +199,6 @@ uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() {
return m_max_packet_size;
}
bool GDBRemoteCommunicationClient::GetReverseContinueSupported() {
if (m_supports_reverse_continue == eLazyBoolCalculate) {
GetRemoteQSupported();
}
return m_supports_reverse_continue == eLazyBoolYes;
}
bool GDBRemoteCommunicationClient::GetReverseStepSupported() {
if (m_supports_reverse_step == eLazyBoolCalculate) {
GetRemoteQSupported();
}
return m_supports_reverse_step == eLazyBoolYes;
}
bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
if (m_supports_not_sending_acks == eLazyBoolCalculate) {
m_send_acks = true;
@@ -309,8 +295,6 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
m_supports_qXfer_siginfo_read = eLazyBoolCalculate;
m_supports_augmented_libraries_svr4_read = eLazyBoolCalculate;
m_uses_native_signals = eLazyBoolCalculate;
m_supports_reverse_continue = eLazyBoolCalculate;
m_supports_reverse_step = eLazyBoolCalculate;
m_supports_qProcessInfoPID = true;
m_supports_qfProcessInfo = true;
m_supports_qUserName = true;
@@ -364,8 +348,6 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_memory_tagging = eLazyBoolNo;
m_supports_qSaveCore = eLazyBoolNo;
m_uses_native_signals = eLazyBoolNo;
m_supports_reverse_continue = eLazyBoolNo;
m_supports_reverse_step = eLazyBoolNo;
m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
// not, we assume no limit
@@ -419,10 +401,6 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_qSaveCore = eLazyBoolYes;
else if (x == "native-signals+")
m_uses_native_signals = eLazyBoolYes;
else if (x == "ReverseContinue+")
m_supports_reverse_continue = eLazyBoolYes;
else if (x == "ReverseStep+")
m_supports_reverse_step = eLazyBoolYes;
// Look for a list of compressions in the features list e.g.
// qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
// deflate,lzma

View File

@@ -331,10 +331,6 @@ public:
bool GetMultiprocessSupported();
bool GetReverseContinueSupported();
bool GetReverseStepSupported();
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -565,8 +561,6 @@ protected:
LazyBool m_supports_memory_tagging = eLazyBoolCalculate;
LazyBool m_supports_qSaveCore = eLazyBoolCalculate;
LazyBool m_uses_native_signals = eLazyBoolCalculate;
LazyBool m_supports_reverse_continue = eLazyBoolCalculate;
LazyBool m_supports_reverse_step = eLazyBoolCalculate;
bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
m_supports_qUserName : 1, m_supports_qGroupName : 1,

View File

@@ -716,7 +716,6 @@ static const char *GetStopReasonString(StopReason stop_reason) {
return "vforkdone";
case eStopReasonInterrupt:
return "async interrupt";
case eStopReasonHistoryBoundary:
case eStopReasonInstrumentation:
case eStopReasonInvalid:
case eStopReasonPlanComplete:

View File

@@ -169,10 +169,6 @@ public:
}
};
std::chrono::seconds ResumeTimeout() {
return std::chrono::seconds(5);
}
} // namespace
static PluginProperties &GetGlobalPluginProperties() {
@@ -1184,11 +1180,10 @@ Status ProcessGDBRemote::WillResume() {
return Status();
}
Status ProcessGDBRemote::DoResume(RunDirection direction) {
Status ProcessGDBRemote::DoResume() {
Status error;
Log *log = GetLog(GDBRLog::Process);
LLDB_LOGF(log, "ProcessGDBRemote::Resume(%s)",
direction == RunDirection::eRunForward ? "" : "reverse");
LLDB_LOGF(log, "ProcessGDBRemote::Resume()");
ListenerSP listener_sp(
Listener::MakeListener("gdb-remote.resume-packet-sent"));
@@ -1202,21 +1197,12 @@ Status ProcessGDBRemote::DoResume(RunDirection direction) {
StreamString continue_packet;
bool continue_packet_error = false;
// Number of threads continuing with "c", i.e. continuing without a signal to deliver.
const size_t num_continue_c_tids = m_continue_c_tids.size();
// Number of threads continuing with "C", i.e. continuing with a signal to deliver.
const size_t num_continue_C_tids = m_continue_C_tids.size();
// Number of threads continuing with "s", i.e. single-stepping.
const size_t num_continue_s_tids = m_continue_s_tids.size();
// Number of threads continuing with "S", i.e. single-stepping with a signal to deliver.
const size_t num_continue_S_tids = m_continue_S_tids.size();
if (direction == RunDirection::eRunForward &&
m_gdb_comm.HasAnyVContSupport()) {
if (m_gdb_comm.HasAnyVContSupport()) {
std::string pid_prefix;
if (m_gdb_comm.GetMultiprocessSupported())
pid_prefix = llvm::formatv("p{0:x-}.", GetID());
if (num_continue_c_tids == num_threads ||
if (m_continue_c_tids.size() == num_threads ||
(m_continue_c_tids.empty() && m_continue_C_tids.empty() &&
m_continue_s_tids.empty() && m_continue_S_tids.empty())) {
// All threads are continuing
@@ -1279,11 +1265,14 @@ Status ProcessGDBRemote::DoResume(RunDirection direction) {
} else
continue_packet_error = true;
if (direction == RunDirection::eRunForward && continue_packet_error) {
if (continue_packet_error) {
// Either no vCont support, or we tried to use part of the vCont packet
// that wasn't supported by the remote GDB server, or it's the reverse
// direction. We need to try and make a simple packet that can do our
// continue.
// that wasn't supported by the remote GDB server. We need to try and
// make a simple packet that can do our continue
const size_t num_continue_c_tids = m_continue_c_tids.size();
const size_t num_continue_C_tids = m_continue_C_tids.size();
const size_t num_continue_s_tids = m_continue_s_tids.size();
const size_t num_continue_S_tids = m_continue_S_tids.size();
if (num_continue_c_tids > 0) {
if (num_continue_c_tids == num_threads) {
// All threads are resuming...
@@ -1374,41 +1363,9 @@ Status ProcessGDBRemote::DoResume(RunDirection direction) {
}
}
if (direction == RunDirection::eRunReverse && continue_packet_error) {
if (num_continue_C_tids > 0 || num_continue_S_tids > 0) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: Signals not supported");
return Status::FromErrorString("can't deliver signals while running in reverse");
}
if (num_continue_s_tids > 0) {
if (num_continue_s_tids > 1) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: can't step multiple threads");
return Status::FromErrorString("can't step multiple threads while reverse-stepping");
}
if (!m_gdb_comm.GetReverseStepSupported()) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: target does not support reverse-stepping");
return Status::FromErrorString("target does not support reverse-stepping");
}
m_gdb_comm.SetCurrentThreadForRun(m_continue_s_tids.front());
continue_packet.PutCString("bs");
} else {
if (!m_gdb_comm.GetReverseContinueSupported()) {
LLDB_LOGF(log, "ProcessGDBRemote::DoResumeReverse: target does not support reverse-continue");
return Status::FromErrorString("target does not support reverse-continue");
}
// All threads continue whether requested or not ---
// we can't change how threads ran in the past.
continue_packet.PutCString("bc");
}
continue_packet_error = false;
}
if (continue_packet_error) {
return Status::FromErrorString("can't make continue packet for this resume");
error =
Status::FromErrorString("can't make continue packet for this resume");
} else {
EventSP event_sp;
if (!m_async_thread.IsJoinable()) {
@@ -1423,7 +1380,7 @@ Status ProcessGDBRemote::DoResume(RunDirection direction) {
std::make_shared<EventDataBytes>(continue_packet.GetString());
m_async_broadcaster.BroadcastEvent(eBroadcastBitAsyncContinue, data_sp);
if (!listener_sp->GetEvent(event_sp, ResumeTimeout())) {
if (!listener_sp->GetEvent(event_sp, std::chrono::seconds(5))) {
error = Status::FromErrorString("Resume timed out.");
LLDB_LOGF(log, "ProcessGDBRemote::DoResume: Resume timed out.");
} else if (event_sp->BroadcasterIs(&m_async_broadcaster)) {
@@ -1906,10 +1863,6 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithException(
*thread_sp, description.c_str()));
handled = true;
} else if (reason == "replaylog") {
thread_sp->SetStopInfo(StopInfo::CreateStopReasonHistoryBoundary(
*thread_sp, description.c_str()));
handled = true;
} else if (reason == "exec") {
did_exec = true;
thread_sp->SetStopInfo(
@@ -2365,8 +2318,6 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
description = std::string(ostr.GetString());
} else if (key.compare("swbreak") == 0 || key.compare("hwbreak") == 0) {
reason = "breakpoint";
} else if (key.compare("replaylog") == 0) {
reason = "replaylog";
} else if (key.compare("library") == 0) {
auto error = LoadModules();
if (error) {

View File

@@ -111,7 +111,7 @@ public:
// Process Control
Status WillResume() override;
Status DoResume(lldb::RunDirection direction) override;
Status DoResume() override;
Status DoHalt(bool &caused_stop) override;

View File

@@ -182,15 +182,10 @@ void ScriptedProcess::DidResume() {
m_pid = GetInterface().GetProcessID();
}
Status ScriptedProcess::DoResume(RunDirection direction) {
Status ScriptedProcess::DoResume() {
LLDB_LOGF(GetLog(LLDBLog::Process), "ScriptedProcess::%s resuming process", __FUNCTION__);
if (direction == RunDirection::eRunForward) {
return GetInterface().Resume();
} else {
return Status::FromErrorStringWithFormatv(
"error: {0} does not support reverse execution of processes", GetPluginName());
}
return GetInterface().Resume();
}
Status ScriptedProcess::DoAttach(const ProcessAttachInfo &attach_info) {

View File

@@ -52,7 +52,7 @@ public:
void DidResume() override;
Status DoResume(lldb::RunDirection direction) override;
Status DoResume() override;
Status DoAttachToProcessWithID(lldb::pid_t pid,
const ProcessAttachInfo &attach_info) override;

View File

@@ -446,8 +446,7 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
m_memory_cache(*this), m_allocated_memory_cache(*this),
m_should_detach(false), m_next_event_action_up(), m_public_run_lock(),
m_private_run_lock(), m_currently_handling_do_on_removals(false),
m_resume_requested(false), m_last_run_direction(eRunForward),
m_interrupt_tid(LLDB_INVALID_THREAD_ID),
m_resume_requested(false), m_interrupt_tid(LLDB_INVALID_THREAD_ID),
m_finalizing(false), m_destructing(false),
m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false),
m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false),
@@ -846,7 +845,6 @@ bool Process::HandleProcessStateChangedEvent(
switch (thread_stop_reason) {
case eStopReasonInvalid:
case eStopReasonNone:
case eStopReasonHistoryBoundary:
break;
case eStopReasonSignal: {
@@ -1354,7 +1352,7 @@ void Process::SetPublicState(StateType new_state, bool restarted) {
}
}
Status Process::Resume(RunDirection direction) {
Status Process::Resume() {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "(plugin = %s) -- locking run lock", GetPluginName().data());
if (!m_public_run_lock.TrySetRunning()) {
@@ -1363,7 +1361,7 @@ Status Process::Resume(RunDirection direction) {
return Status::FromErrorString(
"Resume request failed - process still running.");
}
Status error = PrivateResume(direction);
Status error = PrivateResume();
if (!error.Success()) {
// Undo running state change
m_public_run_lock.SetStopped();
@@ -1371,7 +1369,7 @@ Status Process::Resume(RunDirection direction) {
return error;
}
Status Process::ResumeSynchronous(Stream *stream, RunDirection direction) {
Status Process::ResumeSynchronous(Stream *stream) {
Log *log(GetLog(LLDBLog::State | LLDBLog::Process));
LLDB_LOGF(log, "Process::ResumeSynchronous -- locking run lock");
if (!m_public_run_lock.TrySetRunning()) {
@@ -1384,7 +1382,7 @@ Status Process::ResumeSynchronous(Stream *stream, RunDirection direction) {
Listener::MakeListener(ResumeSynchronousHijackListenerName.data()));
HijackProcessEvents(listener_sp);
Status error = PrivateResume(direction);
Status error = PrivateResume();
if (error.Success()) {
StateType state =
WaitForProcessToStop(std::nullopt, nullptr, true, listener_sp, stream,
@@ -3241,7 +3239,7 @@ Status Process::ConnectRemote(llvm::StringRef remote_url) {
return error;
}
Status Process::PrivateResume(RunDirection direction) {
Status Process::PrivateResume() {
Log *log(GetLog(LLDBLog::Process | LLDBLog::Step));
LLDB_LOGF(log,
"Process::PrivateResume() m_stop_id = %u, public state: %s "
@@ -3257,15 +3255,6 @@ Status Process::PrivateResume(RunDirection direction) {
if (!GetModID().IsLastResumeForUserExpression())
ResetExtendedCrashInfoDict();
if (m_last_run_direction != direction) {
// In the future we might want to support mixed-direction plans,
// e.g. a forward step-over stops at a breakpoint, the user does
// a reverse-step, then disables the breakpoint and continues forward.
// This code will need to be changed to support that.
m_thread_list.DiscardThreadPlans();
m_last_run_direction = direction;
}
Status error(WillResume());
// Tell the process it is about to resume before the thread list
if (error.Success()) {
@@ -3283,7 +3272,7 @@ Status Process::PrivateResume(RunDirection direction) {
"Process::PrivateResume PreResumeActions failed, not resuming.");
} else {
m_mod_id.BumpResumeID();
error = DoResume(direction);
error = DoResume();
if (error.Success()) {
DidResume();
m_thread_list.DidResume();
@@ -3746,7 +3735,7 @@ bool Process::ShouldBroadcastEvent(Event *event_ptr) {
"from state: %s",
static_cast<void *>(event_ptr), StateAsCString(state));
ProcessEventData::SetRestartedInEvent(event_ptr, true);
PrivateResume(m_last_run_direction);
PrivateResume();
}
} else {
return_value = true;
@@ -4357,7 +4346,7 @@ void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) {
SetRestarted(true);
// Use the private resume method here, since we aren't changing the run
// lock state.
process_sp->PrivateResume(process_sp->m_last_run_direction);
process_sp->PrivateResume();
} else {
bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) &&
!process_sp->StateChangedIsHijackedForSynchronousResume();

View File

@@ -1212,30 +1212,6 @@ public:
}
};
// StopInfoHistoryBoundary
class StopInfoHistoryBoundary : public StopInfo {
public:
StopInfoHistoryBoundary(Thread &thread, const char *description)
: StopInfo(thread, LLDB_INVALID_UID) {
if (description)
SetDescription(description);
}
~StopInfoHistoryBoundary() override = default;
StopReason GetStopReason() const override {
return eStopReasonHistoryBoundary;
}
const char *GetDescription() override {
if (m_description.empty())
return "history boundary";
else
return m_description.c_str();
}
};
// StopInfoThreadPlan
class StopInfoThreadPlan : public StopInfo {
@@ -1463,11 +1439,6 @@ StopInfoSP StopInfo::CreateStopReasonProcessorTrace(Thread &thread,
return StopInfoSP(new StopInfoProcessorTrace(thread, description));
}
StopInfoSP StopInfo::CreateStopReasonHistoryBoundary(Thread &thread,
const char *description) {
return StopInfoSP(new StopInfoHistoryBoundary(thread, description));
}
StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) {
return StopInfoSP(new StopInfoExec(thread));
}

View File

@@ -624,12 +624,10 @@ void Thread::SetupForResume() {
// what the current plan is.
lldb::RegisterContextSP reg_ctx_sp(GetRegisterContext());
ProcessSP process_sp(GetProcess());
if (reg_ctx_sp && process_sp &&
process_sp->GetLastRunDirection() == eRunForward) {
if (reg_ctx_sp) {
const addr_t thread_pc = reg_ctx_sp->GetPC();
BreakpointSiteSP bp_site_sp =
process_sp->GetBreakpointSiteList().FindByAddress(thread_pc);
GetProcess()->GetBreakpointSiteList().FindByAddress(thread_pc);
if (bp_site_sp) {
// Note, don't assume there's a ThreadPlanStepOverBreakpoint, the
// target may not require anything special to step over a breakpoint.
@@ -1734,8 +1732,6 @@ std::string Thread::StopReasonAsString(lldb::StopReason reason) {
return "processor trace";
case eStopReasonInterrupt:
return "async interrupt";
case eStopReasonHistoryBoundary:
return "history boundary";
}
return "StopReason = " + std::to_string(reason);

View File

@@ -1,3 +0,0 @@
C_SOURCES := main.c
include Makefile.rules

View File

@@ -1,115 +0,0 @@
import lldb
import time
import unittest
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test.gdbclientutils import *
from lldbsuite.test.lldbreverse import ReverseTestBase
from lldbsuite.test import lldbutil
class TestReverseContinueBreakpoints(ReverseTestBase):
NO_DEBUG_INFO_TESTCASE = True
def test_reverse_continue(self):
self.reverse_continue_internal(async_mode=False)
def test_reverse_continue_async(self):
self.reverse_continue_internal(async_mode=True)
def reverse_continue_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)
# Reverse-continue. We'll stop at the point where we started recording.
status = process.Continue(lldb.eRunReverse)
self.assertSuccess(status)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
self.expect(
"thread list",
STOPPED_DUE_TO_HISTORY_BOUNDARY,
substrs=["stopped", "stop reason = history boundary"],
)
# Continue forward normally until the target exits.
status = process.Continue()
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateExited])
self.assertSuccess(status)
self.assertState(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)
def test_reverse_continue_breakpoint(self):
self.reverse_continue_breakpoint_internal(async_mode=False)
def test_reverse_continue_breakpoint_async(self):
self.reverse_continue_breakpoint_internal(async_mode=True)
def reverse_continue_breakpoint_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)
# Reverse-continue to the function "trigger_breakpoint".
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
status = process.Continue(lldb.eRunReverse)
self.assertSuccess(status)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
threads_now = lldbutil.get_threads_stopped_at_breakpoint(process, trigger_bkpt)
self.assertEqual(threads_now, initial_threads)
def test_reverse_continue_skip_breakpoint(self):
self.reverse_continue_skip_breakpoint_internal(async_mode=False)
def test_reverse_continue_skip_breakpoint_async(self):
self.reverse_continue_skip_breakpoint_internal(async_mode=True)
def reverse_continue_skip_breakpoint_internal(self, async_mode):
target, process, initial_threads = self.setup_recording(async_mode)
# Reverse-continue over a breakpoint at "trigger_breakpoint" whose
# condition is false.
# This tests that we continue in the correct direction after hitting
# the breakpoint.
trigger_bkpt = target.BreakpointCreateByName("trigger_breakpoint", None)
trigger_bkpt.SetCondition("false_condition")
status = process.Continue(lldb.eRunReverse)
self.expect_async_state_changes(async_mode, process, [lldb.eStateRunning, lldb.eStateStopped])
self.assertSuccess(status)
self.expect(
"thread list",
STOPPED_DUE_TO_HISTORY_BOUNDARY,
substrs=["stopped", "stop reason = history boundary"],
)
def setup_recording(self, async_mode):
"""
Record execution of code between "start_recording" and "stop_recording" breakpoints.
Returns with the target stopped at "stop_recording", with recording disabled,
ready to reverse-execute.
"""
self.build()
target = self.dbg.CreateTarget("")
process = self.connect(target)
# Record execution from the start of the function "start_recording"
# to the start of the function "stop_recording". We want to keep the
# interval that we record as small as possible to minimize the run-time
# of our single-stepping recorder.
start_recording_bkpt = target.BreakpointCreateByName("start_recording", None)
initial_threads = lldbutil.continue_to_breakpoint(process, start_recording_bkpt)
self.assertEqual(len(initial_threads), 1)
target.BreakpointDelete(start_recording_bkpt.GetID())
self.start_recording()
stop_recording_bkpt = target.BreakpointCreateByName("stop_recording", None)
lldbutil.continue_to_breakpoint(process, stop_recording_bkpt)
target.BreakpointDelete(stop_recording_bkpt.GetID())
self.stop_recording()
self.dbg.SetAsync(async_mode)
self.expect_async_state_changes(async_mode, process, [lldb.eStateStopped])
return target, process, initial_threads
def expect_async_state_changes(self, async_mode, process, states):
if not async_mode:
return
listener = self.dbg.GetListener()
lldbutil.expect_state_changes(self, listener, process, states)

View File

@@ -1,30 +0,0 @@
import lldb
import unittest
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test import lldbutil
class TestReverseContinueNotSupported(TestBase):
NO_DEBUG_INFO_TESTCASE = True
def test_reverse_continue_not_supported(self):
self.build()
exe = self.getBuildArtifact("a.out")
target = self.dbg.CreateTarget(exe)
self.assertTrue(target, VALID_TARGET)
main_bkpt = target.BreakpointCreateByName("main", None)
self.assertTrue(main_bkpt, VALID_BREAKPOINT)
process = target.LaunchSimple(None, None, self.get_process_working_directory())
self.assertTrue(process, PROCESS_IS_VALID)
# This will fail gracefully.
status = process.Continue(lldb.eRunReverse)
self.assertFailure(status, "target does not support reverse-continue")
status = process.Continue()
self.assertSuccess(status)
self.assertState(process.GetState(), lldb.eStateExited)
self.assertEqual(process.GetExitStatus(), 0)

View File

@@ -1,14 +0,0 @@
volatile int false_condition = 0;
static void start_recording() {}
static void trigger_breakpoint() {}
static void stop_recording() {}
int main() {
start_recording();
trigger_breakpoint();
stop_recording();
return 0;
}

View File

@@ -1045,9 +1045,6 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
case lldb::eStopReasonProcessorTrace:
body.try_emplace("reason", "processor trace");
break;
case lldb::eStopReasonHistoryBoundary:
body.try_emplace("reason", "history boundary");
break;
case lldb::eStopReasonSignal:
case lldb::eStopReasonException:
body.try_emplace("reason", "exception");

View File

@@ -111,7 +111,6 @@ bool ThreadHasStopReason(lldb::SBThread &thread) {
case lldb::eStopReasonVFork:
case lldb::eStopReasonVForkDone:
case lldb::eStopReasonInterrupt:
case lldb::eStopReasonHistoryBoundary:
return true;
case lldb::eStopReasonThreadExiting:
case lldb::eStopReasonInvalid: