mirror of
https://github.com/intel/llvm.git
synced 2026-01-14 11:57:39 +08:00
Revert "[lldb] Implement basic support for reverse-continue (#99736)"
This reverts commit d5e1de6da9.
This commit is contained in:
@@ -159,7 +159,6 @@ public:
|
||||
lldb::SBError Destroy();
|
||||
|
||||
lldb::SBError Continue();
|
||||
lldb::SBError Continue(RunDirection direction);
|
||||
|
||||
lldb::SBError Stop();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user