Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lldb/include/lldb/API/SBProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class LLDB_API SBProcess {
lldb::SBError Destroy();

lldb::SBError Continue();
lldb::SBError ContinueInDirection(lldb::RunDirection direction);

lldb::SBError Stop();

Expand Down
28 changes: 26 additions & 2 deletions lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,13 @@ class Process : public std::enable_shared_from_this<Process>,
/// Returns an error object.
virtual Status WillResume() { return Status(); }

/// Reports whether this process supports reverse execution.
///
/// \return
/// Returns true if the process supports reverse execution (at least
/// under some circumstances).
virtual bool SupportsReverseDirection() { return false; }

/// Resumes all of a process's threads as configured using the Thread run
/// control functions.
///
Expand All @@ -1104,9 +1111,13 @@ class Process : public std::enable_shared_from_this<Process>,
/// \see Thread:Resume()
/// \see Thread:Step()
/// \see Thread:Suspend()
virtual Status DoResume() {
virtual Status DoResume(lldb::RunDirection direction) {
if (direction == lldb::RunDirection::eRunForward)
return Status::FromErrorStringWithFormatv(
"error: {0} does not support resuming processes", GetPluginName());
return Status::FromErrorStringWithFormatv(
"error: {0} does not support resuming processes", GetPluginName());
"error: {0} does not support reverse execution of processes",
GetPluginName());
}

/// Called after resuming a process.
Expand Down Expand Up @@ -2677,6 +2688,18 @@ void PruneThreadPlans();
const AddressRange &range, size_t alignment,
Status &error);

/// Get the base run direction for the process.
/// The base direction is the direction the process will execute in
/// (forward or backward) if no thread plan overrides the direction.
lldb::RunDirection GetBaseDirection() const { return m_base_direction; }
/// Set the base run direction for the process.
/// As a side-effect, if this changes the base direction, then we
/// discard all non-base thread plans to ensure that when execution resumes
/// we definitely execute in the requested direction.
/// FIXME: this is overkill. In some situations ensuring the latter
/// would not require discarding all non-base thread plans.
void SetBaseDirection(lldb::RunDirection direction);

protected:
friend class Trace;

Expand Down Expand Up @@ -3076,6 +3099,7 @@ void PruneThreadPlans();
ThreadList
m_extended_thread_list; ///< Constituent for extended threads that may be
/// generated, cleared on natural stops
lldb::RunDirection m_base_direction; ///< ThreadPlanBase run direction
uint32_t m_extended_thread_stop_id; ///< The natural stop id when
///extended_thread_list was last updated
QueueList
Expand Down
7 changes: 7 additions & 0 deletions lldb/include/lldb/Target/StopInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace lldb_private {
class StopInfo : public std::enable_shared_from_this<StopInfo> {
friend class Process::ProcessEventData;
friend class ThreadPlanBase;
friend class ThreadPlanReverseContinue;

public:
// Constructors and Destructors
Expand Down Expand Up @@ -154,6 +155,12 @@ class StopInfo : public std::enable_shared_from_this<StopInfo> {
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);
Expand Down
9 changes: 4 additions & 5 deletions lldb/include/lldb/Target/Thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,13 @@ class Thread : public std::enable_shared_from_this<Thread>,
/// The User resume state for this thread.
lldb::StateType GetResumeState() const { return m_resume_state; }

/// This function is called on all the threads before "ShouldResume" and
/// "WillResume" in case a thread needs to change its state before the
/// ThreadList polls all the threads to figure out which ones actually will
/// get to run and how.
// This function is called to determine whether the thread needs to
// step over a breakpoint and if so, push a step-over-breakpoint thread
// plan.
///
/// \return
/// True if we pushed a ThreadPlanStepOverBreakpoint
bool SetupForResume();
bool SetupToStepOverBreakpointIfNeeded(lldb::RunDirection direction);

// Do not override this function, it is for thread plan logic only
bool ShouldResume(lldb::StateType resume_state);
Expand Down
6 changes: 5 additions & 1 deletion lldb/include/lldb/Target/ThreadList.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,18 @@ class ThreadList : public ThreadCollection {
/// If a thread can "resume" without having to resume the target, it
/// will return false for WillResume, and then the process will not be
/// restarted.
/// Sets *direction to the run direction of the thread(s) that will
/// be resumed. If threads that we want to run disagree about the
/// direction, we execute forwards and pop any of the thread plans
/// that requested reverse execution.
///
/// \return
/// \b true instructs the process to resume normally,
/// \b false means start & stopped events will be generated, but
/// the process will not actually run. The thread must then return
/// the correct StopInfo when asked.
///
bool WillResume();
bool WillResume(lldb::RunDirection &direction);

void DidResume();

Expand Down
13 changes: 13 additions & 0 deletions lldb/include/lldb/Target/ThreadPlan.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ namespace lldb_private {
// report_run_vote argument to the constructor works like report_stop_vote, and
// is a way for a plan to instruct a sub-plan on how to respond to
// ShouldReportStop.
//
// Reverse execution:
//
// Every thread plan has an associated RunDirection (forward or backward).
// For ThreadPlanBase, this direction is the Process's base direction.
// Whenever we resume the target, we need to ensure that the topmost thread
// plans for each runnable thread all agree on their direction. This is
// ensured in ThreadList::WillResume(), which chooses a direction and then
// discards thread plans incompatible with that direction.

class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>,
public UserID {
Expand Down Expand Up @@ -497,6 +506,10 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>,

virtual lldb::StateType GetPlanRunState() = 0;

virtual lldb::RunDirection GetDirection() const {
return lldb::RunDirection::eRunForward;
}

protected:
// Constructors and Destructors
ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread,
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/ThreadPlanBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class ThreadPlanBase : public ThreadPlan {

bool IsBasePlan() override { return true; }

lldb::RunDirection GetDirection() const override;

protected:
bool DoWillResume(lldb::StateType resume_state, bool current_plan) override;
bool DoPlanExplainsStop(Event *event_ptr) override;
Expand Down
6 changes: 6 additions & 0 deletions lldb/include/lldb/lldb-enumerations.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ FLAGS_ENUM(LaunchFlags){
/// Thread Run Modes.
enum RunMode { eOnlyThisThread, eAllThreads, eOnlyDuringStepping };

/// Execution directions
enum RunDirection { eRunForward, eRunReverse };

/// Byte ordering definitions.
enum ByteOrder {
eByteOrderInvalid = 0,
Expand Down Expand Up @@ -254,6 +257,9 @@ 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.
Expand Down
5 changes: 3 additions & 2 deletions lldb/packages/Python/lldbsuite/test/gdbclientutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,9 @@ def start(self):
self._thread.start()

def stop(self):
self._thread.join()
self._thread = None
if self._thread is not None:
self._thread.join()
self._thread = None

def get_connect_address(self):
return self._socket.get_connect_address()
Expand Down
177 changes: 177 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbgdbproxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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__)

self.logger.propagate = False
self.logger.setLevel(logging.DEBUG)

# log all warnings to stderr
self._stderr_log_handler = logging.StreamHandler()
self._stderr_log_handler.setLevel(logging.DEBUG if self.TraceOn() else logging.WARNING)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Apply this if you want to dump traces unconditionally.

Suggested change
self._stderr_log_handler.setLevel(logging.DEBUG if self.TraceOn() else logging.WARNING)
self._stderr_log_handler.setLevel(logging.DEBUG)

self._stderr_log_handler.setFormatter(self._log_formatter)
self.logger.addHandler(self._stderr_log_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)

if lldbplatformutil.getPlatform() == "macosx":
self.debug_monitor_exe = lldbgdbserverutils.get_debugserver_exe()
self.debug_monitor_extra_args = []
else:
self.debug_monitor_exe = lldbgdbserverutils.get_lldb_server_exe()
self.debug_monitor_extra_args = ["gdbserver"]
self.assertIsNotNone(self.debug_monitor_exe)

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
self.logger.removeHandler(self._stderr_log_handler)
self._stderr_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 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.set_validate_checksums(False)
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)
# Turn off checksum validation because debugserver does not produce
# correct checksums.
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
Loading
Loading