Skip to content

Commit 9f33f8e

Browse files
committed
[lldb] Implement basic support for reverse-continue
This commit only adds support for the `SBProcess::ReverseContinue()` API. A user-accessible command for this will follow in a later commit. This feature depends on a gdbserver implementation (e.g. `rr`) providing support for the `bc` and `bs` packets. `lldb-server` does not support those packets, and there is no plan to change that. So, for testing purposes, `lldbreverse.py` wraps `lldb-server` with a Python implementation of *very limited* record-and-replay functionality.
1 parent 79d695f commit 9f33f8e

32 files changed

+1035
-50
lines changed

lldb/include/lldb/API/SBProcess.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class LLDB_API SBProcess {
159159
lldb::SBError Destroy();
160160

161161
lldb::SBError Continue();
162+
lldb::SBError Continue(RunDirection direction);
162163

163164
lldb::SBError Stop();
164165

lldb/include/lldb/Target/Process.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -857,10 +857,11 @@ class Process : public std::enable_shared_from_this<Process>,
857857
/// \see Thread:Resume()
858858
/// \see Thread:Step()
859859
/// \see Thread:Suspend()
860-
Status Resume();
860+
Status Resume(lldb::RunDirection direction = lldb::eRunForward);
861861

862862
/// Resume a process, and wait for it to stop.
863-
Status ResumeSynchronous(Stream *stream);
863+
Status ResumeSynchronous(Stream *stream,
864+
lldb::RunDirection direction = lldb::eRunForward);
864865

865866
/// Halts a running process.
866867
///
@@ -1104,9 +1105,15 @@ class Process : public std::enable_shared_from_this<Process>,
11041105
/// \see Thread:Resume()
11051106
/// \see Thread:Step()
11061107
/// \see Thread:Suspend()
1107-
virtual Status DoResume() {
1108-
return Status::FromErrorStringWithFormatv(
1109-
"error: {0} does not support resuming processes", GetPluginName());
1108+
virtual Status DoResume(lldb::RunDirection direction) {
1109+
if (direction == lldb::RunDirection::eRunForward) {
1110+
return Status::FromErrorStringWithFormatv(
1111+
"error: {0} does not support resuming processes", GetPluginName());
1112+
} else {
1113+
return Status::FromErrorStringWithFormatv(
1114+
"error: {0} does not support reverse execution of processes",
1115+
GetPluginName());
1116+
}
11101117
}
11111118

11121119
/// Called after resuming a process.
@@ -2332,6 +2339,8 @@ class Process : public std::enable_shared_from_this<Process>,
23322339

23332340
bool IsRunning() const;
23342341

2342+
lldb::RunDirection GetLastRunDirection() { return m_last_run_direction; }
2343+
23352344
DynamicCheckerFunctions *GetDynamicCheckers() {
23362345
return m_dynamic_checkers_up.get();
23372346
}
@@ -2851,7 +2860,7 @@ void PruneThreadPlans();
28512860
///
28522861
/// \return
28532862
/// An Status object describing the success or failure of the resume.
2854-
Status PrivateResume();
2863+
Status PrivateResume(lldb::RunDirection direction = lldb::eRunForward);
28552864

28562865
// Called internally
28572866
void CompleteAttach();
@@ -3127,6 +3136,8 @@ void PruneThreadPlans();
31273136
// m_currently_handling_do_on_removals are true,
31283137
// Resume will only request a resume, using this
31293138
// flag to check.
3139+
// The direction of execution from the last time this process was resumed.
3140+
lldb::RunDirection m_last_run_direction;
31303141

31313142
lldb::tid_t m_interrupt_tid; /// The tid of the thread that issued the async
31323143
/// interrupt, used by thread plan timeout. It

lldb/include/lldb/Target/StopInfo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ class StopInfo : public std::enable_shared_from_this<StopInfo> {
142142
static lldb::StopInfoSP
143143
CreateStopReasonProcessorTrace(Thread &thread, const char *description);
144144

145+
// This creates a StopInfo indicating that execution stopped because
146+
// it was replaying some recorded execution history, and execution reached
147+
// the end of that recorded history.
148+
static lldb::StopInfoSP
149+
CreateStopReasonHistoryBoundary(Thread &thread, const char *description);
150+
145151
static lldb::StopInfoSP CreateStopReasonFork(Thread &thread,
146152
lldb::pid_t child_pid,
147153
lldb::tid_t child_tid);

lldb/include/lldb/lldb-enumerations.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ FLAGS_ENUM(LaunchFlags){
135135
/// Thread Run Modes.
136136
enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };
137137

138+
/// Execution directions
139+
enum RunDirection { eRunForward, eRunReverse };
140+
138141
/// Byte ordering definitions.
139142
enum ByteOrder {
140143
eByteOrderInvalid = 0,
@@ -254,6 +257,9 @@ enum StopReason {
254257
eStopReasonVFork,
255258
eStopReasonVForkDone,
256259
eStopReasonInterrupt, ///< Thread requested interrupt
260+
// Indicates that execution stopped because the debugger backend relies
261+
// on recorded data and we reached the end of that data.
262+
eStopReasonHistoryBoundary,
257263
};
258264

259265
/// Command Return Status Types.

lldb/packages/Python/lldbsuite/test/gdbclientutils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,9 @@ def start(self):
510510
self._thread.start()
511511

512512
def stop(self):
513-
self._thread.join()
514-
self._thread = None
513+
if self._thread is not None:
514+
self._thread.join()
515+
self._thread = None
515516

516517
def get_connect_address(self):
517518
return self._socket.get_connect_address()
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import logging
2+
import os
3+
import os.path
4+
import random
5+
6+
import lldb
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test.gdbclientutils import *
9+
import lldbgdbserverutils
10+
from lldbsuite.support import seven
11+
12+
13+
class GDBProxyTestBase(TestBase):
14+
"""
15+
Base class for gdbserver proxy tests.
16+
17+
This class will setup and start a mock GDB server for the test to use.
18+
It pases through requests to a regular lldb-server/debugserver and
19+
forwards replies back to the LLDB under test.
20+
"""
21+
22+
"""The gdbserver that we implement."""
23+
server = None
24+
"""The inner lldb-server/debugserver process that we proxy requests into."""
25+
monitor_server = None
26+
monitor_sock = None
27+
28+
server_socket_class = TCPServerSocket
29+
30+
DEFAULT_TIMEOUT = 20 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
31+
32+
_verbose_log_handler = None
33+
_log_formatter = logging.Formatter(fmt="%(asctime)-15s %(levelname)-8s %(message)s")
34+
35+
def setUpBaseLogging(self):
36+
self.logger = logging.getLogger(__name__)
37+
38+
if len(self.logger.handlers) > 0:
39+
return # We have set up this handler already
40+
41+
self.logger.propagate = False
42+
self.logger.setLevel(logging.DEBUG)
43+
44+
# log all warnings to stderr
45+
handler = logging.StreamHandler()
46+
handler.setLevel(logging.WARNING)
47+
handler.setFormatter(self._log_formatter)
48+
self.logger.addHandler(handler)
49+
50+
def setUp(self):
51+
TestBase.setUp(self)
52+
53+
self.setUpBaseLogging()
54+
55+
if self.isVerboseLoggingRequested():
56+
# If requested, full logs go to a log file
57+
log_file_name = self.getLogBasenameForCurrentTest() + "-proxy.log"
58+
self._verbose_log_handler = logging.FileHandler(log_file_name)
59+
self._verbose_log_handler.setFormatter(self._log_formatter)
60+
self._verbose_log_handler.setLevel(logging.DEBUG)
61+
self.logger.addHandler(self._verbose_log_handler)
62+
63+
lldb_server_exe = lldbgdbserverutils.get_lldb_server_exe()
64+
if lldb_server_exe is None:
65+
self.debug_monitor_exe = lldbgdbserverutils.get_debugserver_exe()
66+
self.assertTrue(self.debug_monitor_exe is not None)
67+
self.debug_monitor_extra_args = []
68+
else:
69+
self.debug_monitor_exe = lldb_server_exe
70+
self.debug_monitor_extra_args = ["gdbserver"]
71+
72+
self.server = MockGDBServer(self.server_socket_class())
73+
self.server.responder = self
74+
75+
def tearDown(self):
76+
# TestBase.tearDown will kill the process, but we need to kill it early
77+
# so its client connection closes and we can stop the server before
78+
# finally calling the base tearDown.
79+
if self.process() is not None:
80+
self.process().Kill()
81+
self.server.stop()
82+
83+
self.logger.removeHandler(self._verbose_log_handler)
84+
self._verbose_log_handler = None
85+
86+
TestBase.tearDown(self)
87+
88+
def isVerboseLoggingRequested(self):
89+
# We will report our detailed logs if the user requested that the "gdb-remote" channel is
90+
# logged.
91+
return any(("gdb-remote" in channel) for channel in lldbtest_config.channels)
92+
93+
def connect(self, target):
94+
"""
95+
Create a process by connecting to the mock GDB server.
96+
"""
97+
self.prep_debug_monitor_and_inferior()
98+
self.server.start()
99+
100+
listener = self.dbg.GetListener()
101+
error = lldb.SBError()
102+
process = target.ConnectRemote(
103+
listener, self.server.get_connect_url(), "gdb-remote", error
104+
)
105+
self.assertTrue(error.Success(), error.description)
106+
self.assertTrue(process, PROCESS_IS_VALID)
107+
return process
108+
109+
def get_next_port(self):
110+
return 12000 + random.randint(0, 3999)
111+
112+
def prep_debug_monitor_and_inferior(self):
113+
inferior_exe_path = self.getBuildArtifact("a.out")
114+
self.connect_to_debug_monitor([inferior_exe_path])
115+
self.assertIsNotNone(self.monitor_server)
116+
self.initial_handshake()
117+
118+
def initial_handshake(self):
119+
self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
120+
reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
121+
self.assertEqual(reply, "+")
122+
self.monitor_server.send_packet(seven.bitcast_to_bytes("QStartNoAckMode"))
123+
reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
124+
self.assertEqual(reply, "+")
125+
reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
126+
self.assertEqual(reply, "OK")
127+
self.monitor_server.send_packet(seven.bitcast_to_bytes("+"))
128+
reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
129+
self.assertEqual(reply, "+")
130+
131+
def get_debug_monitor_command_line_args(self, connect_address, launch_args):
132+
return (
133+
self.debug_monitor_extra_args
134+
+ ["--reverse-connect", connect_address]
135+
+ launch_args
136+
)
137+
138+
def launch_debug_monitor(self, launch_args):
139+
family, type, proto, _, addr = socket.getaddrinfo(
140+
"localhost", 0, proto=socket.IPPROTO_TCP
141+
)[0]
142+
sock = socket.socket(family, type, proto)
143+
sock.settimeout(self.DEFAULT_TIMEOUT)
144+
sock.bind(addr)
145+
sock.listen(1)
146+
addr = sock.getsockname()
147+
connect_address = "[{}]:{}".format(*addr)
148+
149+
commandline_args = self.get_debug_monitor_command_line_args(
150+
connect_address, launch_args
151+
)
152+
153+
# Start the server.
154+
self.logger.info(f"Spawning monitor {commandline_args}")
155+
monitor_process = self.spawnSubprocess(
156+
self.debug_monitor_exe, commandline_args, install_remote=False
157+
)
158+
self.assertIsNotNone(monitor_process)
159+
160+
self.monitor_sock = sock.accept()[0]
161+
self.monitor_sock.settimeout(self.DEFAULT_TIMEOUT)
162+
return monitor_process
163+
164+
def connect_to_debug_monitor(self, launch_args):
165+
monitor_process = self.launch_debug_monitor(launch_args)
166+
self.monitor_server = lldbgdbserverutils.Server(
167+
self.monitor_sock, monitor_process
168+
)
169+
170+
def respond(self, packet):
171+
"""Subclasses can override this to change how packets are handled."""
172+
return self.pass_through(packet)
173+
174+
def pass_through(self, packet):
175+
self.logger.info(f"Sending packet {packet}")
176+
self.monitor_server.send_packet(seven.bitcast_to_bytes(packet))
177+
reply = seven.bitcast_to_string(self.monitor_server.get_normal_packet())
178+
self.logger.info(f"Received reply {reply}")
179+
return reply

0 commit comments

Comments
 (0)