-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb] Implement basic support for reverse-continue #99736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -857,10 +857,10 @@ class Process : public std::enable_shared_from_this<Process>, | |
/// \see Thread:Resume() | ||
/// \see Thread:Step() | ||
/// \see Thread:Suspend() | ||
Status Resume(); | ||
Status Resume(lldb::RunDirection direction = lldb::eRunForward); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. internal APIs, anything not in the
I am open to feedback from other here as my mind can easily be changed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The decision was to use a direction flag. |
||
|
||
/// Resume a process, and wait for it to stop. | ||
Status ResumeSynchronous(Stream *stream); | ||
Status ResumeSynchronous(Stream *stream, lldb::RunDirection direction = lldb::eRunForward); | ||
|
||
/// Halts a running process. | ||
/// | ||
|
@@ -1104,9 +1104,14 @@ class Process : public std::enable_shared_from_this<Process>, | |
/// \see Thread:Resume() | ||
/// \see Thread:Step() | ||
/// \see Thread:Suspend() | ||
virtual Status DoResume() { | ||
return Status::FromErrorStringWithFormatv( | ||
"error: {0} does not support resuming processes", GetPluginName()); | ||
virtual Status DoResume(lldb::RunDirection direction) { | ||
rocallahan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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()); | ||
} | ||
} | ||
|
||
/// Called after resuming a process. | ||
|
@@ -2332,6 +2337,8 @@ class Process : public std::enable_shared_from_this<Process>, | |
|
||
bool IsRunning() const; | ||
|
||
lldb::RunDirection GetLastRunDirection() { return m_last_run_direction; } | ||
|
||
DynamicCheckerFunctions *GetDynamicCheckers() { | ||
return m_dynamic_checkers_up.get(); | ||
} | ||
|
@@ -2851,7 +2858,7 @@ void PruneThreadPlans(); | |
/// | ||
/// \return | ||
/// An Status object describing the success or failure of the resume. | ||
Status PrivateResume(); | ||
Status PrivateResume(lldb::RunDirection direction = lldb::eRunForward); | ||
|
||
// Called internally | ||
void CompleteAttach(); | ||
|
@@ -3127,6 +3134,8 @@ void PruneThreadPlans(); | |
// 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; | ||
rocallahan marked this conversation as resolved.
Show resolved
Hide resolved
rocallahan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
lldb::tid_t m_interrupt_tid; /// The tid of the thread that issued the async | ||
/// interrupt, used by thread plan timeout. It | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -510,8 +510,9 @@ def start(self): | |
self._thread.start() | ||
|
||
def stop(self): | ||
self._thread.join() | ||
self._thread = None | ||
if self._thread is not None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not obvious when you'd get a stop with no thread. Why did you need this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a debugger stop and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, cool. |
||
self._thread.join() | ||
self._thread = None | ||
|
||
def get_connect_address(self): | ||
return self._socket.get_connect_address() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
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 |
Uh oh!
There was an error while loading. Please reload this page.