llgs: add --reverse-connect support.
Also includes --reverse-connect tests for llgs and debugserver. llvm-svn: 214031
This commit is contained in:
parent
7b70cadae9
commit
31bde322f3
|
@ -0,0 +1,86 @@
|
|||
# Add the directory above ours to the python library path since we
|
||||
# will import from there.
|
||||
import os.path
|
||||
import sys
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
import gdbremote_testcase
|
||||
import re
|
||||
import select
|
||||
import socket
|
||||
import time
|
||||
from lldbtest import *
|
||||
|
||||
class TestStubReverseConnect(gdbremote_testcase.GdbRemoteTestCaseBase):
|
||||
_DEFAULT_TIMEOUT = 20
|
||||
|
||||
def setUp(self):
|
||||
# Set up the test.
|
||||
gdbremote_testcase.GdbRemoteTestCaseBase.setUp(self)
|
||||
|
||||
# Create a listener on a local port.
|
||||
self.listener_socket = self.create_listener_socket()
|
||||
self.assertIsNotNone(self.listener_socket)
|
||||
self.listener_port = self.listener_socket.getsockname()[1]
|
||||
|
||||
def create_listener_socket(self, timeout_seconds=_DEFAULT_TIMEOUT):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.assertIsNotNone(sock)
|
||||
|
||||
sock.settimeout(timeout_seconds)
|
||||
sock.bind(("127.0.0.1",0))
|
||||
sock.listen(1)
|
||||
|
||||
def tear_down_listener():
|
||||
try:
|
||||
sock.shutdown(socket.SHUT_RDWR)
|
||||
except:
|
||||
# ignore
|
||||
None
|
||||
|
||||
self.addTearDownHook(tear_down_listener)
|
||||
return sock
|
||||
|
||||
def reverse_connect_works(self):
|
||||
# Indicate stub startup should do a reverse connect.
|
||||
appended_stub_args = " --reverse-connect"
|
||||
if self.debug_monitor_extra_args:
|
||||
self.debug_monitor_extra_args += appended_stub_args
|
||||
else:
|
||||
self.debug_monitor_extra_args = appended_stub_args
|
||||
|
||||
self.stub_hostname = "127.0.0.1"
|
||||
self.port = self.listener_port
|
||||
|
||||
# Start the stub.
|
||||
server = self.launch_debug_monitor(logfile=sys.stdout)
|
||||
self.assertIsNotNone(server)
|
||||
self.assertTrue(server.isalive())
|
||||
|
||||
# Listen for the stub's connection to us.
|
||||
(stub_socket, address) = self.listener_socket.accept()
|
||||
self.assertIsNotNone(stub_socket)
|
||||
self.assertIsNotNone(address)
|
||||
print "connected to stub {} on {}".format(address, stub_socket.getsockname())
|
||||
|
||||
# Verify we can do the handshake. If that works, we'll call it good.
|
||||
self.do_handshake(stub_socket, timeout_seconds=self._DEFAULT_TIMEOUT)
|
||||
|
||||
# Clean up.
|
||||
stub_socket.shutdown(socket.SHUT_RDWR)
|
||||
|
||||
@debugserver_test
|
||||
def test_reverse_connect_works_debugserver(self):
|
||||
self.init_debugserver_test(use_named_pipe=False)
|
||||
self.set_inferior_startup_launch()
|
||||
self.reverse_connect_works()
|
||||
|
||||
@llgs_test
|
||||
def test_reverse_connect_works_llgs(self):
|
||||
self.init_llgs_test(use_named_pipe=False)
|
||||
self.set_inferior_startup_launch()
|
||||
self.reverse_connect_works()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
|
@ -8,6 +8,7 @@ import os.path
|
|||
import platform
|
||||
import random
|
||||
import re
|
||||
import select
|
||||
import sets
|
||||
import signal
|
||||
import socket
|
||||
|
@ -55,6 +56,7 @@ class GdbRemoteTestCaseBase(TestBase):
|
|||
self.named_pipe = None
|
||||
self.named_pipe_fd = None
|
||||
self.stub_sends_two_stop_notifications_on_kill = False
|
||||
self.stub_hostname = "localhost"
|
||||
|
||||
def get_next_port(self):
|
||||
return 12000 + random.randint(0,3999)
|
||||
|
@ -165,7 +167,7 @@ class GdbRemoteTestCaseBase(TestBase):
|
|||
|
||||
self.addTearDownHook(shutdown_socket)
|
||||
|
||||
connect_info = ("localhost", self.port)
|
||||
connect_info = (self.stub_hostname, self.port)
|
||||
# print "connecting to stub on {}:{}".format(connect_info[0], connect_info[1])
|
||||
sock.connect(connect_info)
|
||||
|
||||
|
@ -177,17 +179,21 @@ class GdbRemoteTestCaseBase(TestBase):
|
|||
def set_inferior_startup_attach(self):
|
||||
self._inferior_startup = self._STARTUP_ATTACH
|
||||
|
||||
def launch_debug_monitor(self, attach_pid=None):
|
||||
# Create the command line.
|
||||
import pexpect
|
||||
def get_debug_monitor_command_line(self, attach_pid=None):
|
||||
commandline = "{}{} localhost:{}".format(self.debug_monitor_exe, self.debug_monitor_extra_args, self.port)
|
||||
if attach_pid:
|
||||
commandline += " --attach=%d" % attach_pid
|
||||
if self.named_pipe_path:
|
||||
commandline += " --named-pipe %s" % self.named_pipe_path
|
||||
return commandline
|
||||
|
||||
def launch_debug_monitor(self, attach_pid=None, logfile=None):
|
||||
# Create the command line.
|
||||
import pexpect
|
||||
commandline = self.get_debug_monitor_command_line(attach_pid=attach_pid)
|
||||
|
||||
# Start the server.
|
||||
server = pexpect.spawn(commandline)
|
||||
server = pexpect.spawn(commandline, logfile=logfile)
|
||||
self.assertIsNotNone(server)
|
||||
server.expect(r"(debugserver|lldb-gdbserver)", timeout=10)
|
||||
|
||||
|
@ -332,6 +338,45 @@ class GdbRemoteTestCaseBase(TestBase):
|
|||
|
||||
return {"inferior":inferior, "server":server}
|
||||
|
||||
def expect_socket_recv(self, sock, expected_content_regex, timeout_seconds):
|
||||
response = ""
|
||||
timeout_time = time.time() + timeout_seconds
|
||||
|
||||
while not expected_content_regex.match(response) and time.time() < timeout_time:
|
||||
can_read, _, _ = select.select([sock], [], [], timeout_seconds)
|
||||
if can_read and sock in can_read:
|
||||
recv_bytes = sock.recv(4096)
|
||||
if recv_bytes:
|
||||
response += recv_bytes
|
||||
|
||||
self.assertTrue(expected_content_regex.match(response))
|
||||
|
||||
def expect_socket_send(self, sock, content, timeout_seconds):
|
||||
request_bytes_remaining = content
|
||||
timeout_time = time.time() + timeout_seconds
|
||||
|
||||
while len(request_bytes_remaining) > 0 and time.time() < timeout_time:
|
||||
_, can_write, _ = select.select([], [sock], [], timeout_seconds)
|
||||
if can_write and sock in can_write:
|
||||
written_byte_count = sock.send(request_bytes_remaining)
|
||||
request_bytes_remaining = request_bytes_remaining[written_byte_count:]
|
||||
self.assertEquals(len(request_bytes_remaining), 0)
|
||||
|
||||
def do_handshake(self, stub_socket, timeout_seconds=5):
|
||||
# Write the ack.
|
||||
self.expect_socket_send(stub_socket, "+", timeout_seconds)
|
||||
|
||||
# Send the start no ack mode packet.
|
||||
NO_ACK_MODE_REQUEST = "$QStartNoAckMode#b0"
|
||||
bytes_sent = stub_socket.send(NO_ACK_MODE_REQUEST)
|
||||
self.assertEquals(bytes_sent, len(NO_ACK_MODE_REQUEST))
|
||||
|
||||
# Receive the ack and "OK"
|
||||
self.expect_socket_recv(stub_socket, re.compile(r"^\+\$OK#[0-9a-fA-F]{2}$"), timeout_seconds)
|
||||
|
||||
# Send the final ack.
|
||||
self.expect_socket_send(stub_socket, "+", timeout_seconds)
|
||||
|
||||
def add_no_ack_remote_stream(self):
|
||||
self.test_sequence.add_log_lines(
|
||||
["read packet: +",
|
||||
|
|
|
@ -6,7 +6,6 @@ import os.path
|
|||
import platform
|
||||
import Queue
|
||||
import re
|
||||
import select
|
||||
import socket_packet_pump
|
||||
import subprocess
|
||||
import time
|
||||
|
|
|
@ -53,9 +53,9 @@ using namespace lldb_private;
|
|||
|
||||
namespace
|
||||
{
|
||||
static lldb::thread_t s_listen_thread = LLDB_INVALID_HOST_THREAD;
|
||||
static std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up;
|
||||
static std::string s_listen_url;
|
||||
lldb::thread_t s_listen_thread = LLDB_INVALID_HOST_THREAD;
|
||||
std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up;
|
||||
std::string s_listen_url;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
@ -76,6 +76,7 @@ static struct option g_long_options[] =
|
|||
{ "attach", required_argument, NULL, 'a' },
|
||||
{ "named-pipe", required_argument, NULL, 'P' },
|
||||
{ "native-regs", no_argument, NULL, 'r' }, // Specify to use the native registers instead of the gdb defaults for the architecture. NOTE: this is a do-nothing arg as it's behavior is default now. FIXME remove call from lldb-platform.
|
||||
{ "reverse-connect", no_argument, NULL, 'R' }, // Specifies that llgs attaches to the client address:port rather than llgs listening for a connection from address on port.
|
||||
{ "setsid", no_argument, NULL, 'S' }, // Call setsid() to make llgs run in its own session.
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
@ -322,16 +323,17 @@ JoinListenThread ()
|
|||
}
|
||||
|
||||
void
|
||||
start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host_and_port, const char *const progname, const char *const named_pipe_path)
|
||||
ConnectToRemote (GDBRemoteCommunicationServer &gdb_server, bool reverse_connect, const char *const host_and_port, const char *const progname, const char *const named_pipe_path)
|
||||
{
|
||||
Error error;
|
||||
|
||||
if (host_and_port && host_and_port[0])
|
||||
{
|
||||
// Parse out host and port.
|
||||
std::string final_host_and_port;
|
||||
std::string listening_host;
|
||||
std::string listening_port;
|
||||
uint32_t listening_portno = 0;
|
||||
std::string connection_host;
|
||||
std::string connection_port;
|
||||
uint32_t connection_portno = 0;
|
||||
|
||||
// If host_and_port starts with ':', default the host to be "localhost" and expect the remainder to be the port.
|
||||
if (host_and_port[0] == ':')
|
||||
|
@ -341,9 +343,9 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host
|
|||
const std::string::size_type colon_pos = final_host_and_port.find (':');
|
||||
if (colon_pos != std::string::npos)
|
||||
{
|
||||
listening_host = final_host_and_port.substr (0, colon_pos);
|
||||
listening_port = final_host_and_port.substr (colon_pos + 1);
|
||||
listening_portno = Args::StringToUInt32 (listening_port.c_str (), 0);
|
||||
connection_host = final_host_and_port.substr (0, colon_pos);
|
||||
connection_port = final_host_and_port.substr (colon_pos + 1);
|
||||
connection_portno = Args::StringToUInt32 (connection_port.c_str (), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -352,51 +354,89 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host
|
|||
exit (1);
|
||||
}
|
||||
|
||||
// Start the listener on a new thread. We need to do this so we can resolve the
|
||||
// bound listener port.
|
||||
StartListenThread(listening_host.c_str (), static_cast<uint16_t> (listening_portno));
|
||||
printf ("Listening to port %s for a connection from %s...\n", listening_port.c_str (), listening_host.c_str ());
|
||||
|
||||
// If we have a named pipe to write the port number back to, do that now.
|
||||
if (named_pipe_path && named_pipe_path[0] && listening_portno == 0)
|
||||
if (reverse_connect)
|
||||
{
|
||||
// FIXME use new generic named pipe support.
|
||||
int fd = ::open(named_pipe_path, O_WRONLY);
|
||||
if (fd > -1)
|
||||
// llgs will connect to the gdb-remote client.
|
||||
|
||||
// Ensure we have a port number for the connection.
|
||||
if (connection_portno == 0)
|
||||
{
|
||||
const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10);
|
||||
|
||||
char port_str[64];
|
||||
const ssize_t port_str_len = ::snprintf (port_str, sizeof(port_str), "%u", bound_port);
|
||||
// Write the port number as a C string with the NULL terminator.
|
||||
::write (fd, port_str, port_str_len + 1);
|
||||
close (fd);
|
||||
fprintf (stderr, "error: port number must be specified on when using reverse connect");
|
||||
exit (1);
|
||||
}
|
||||
else
|
||||
|
||||
// Build the connection string.
|
||||
char connection_url[512];
|
||||
snprintf(connection_url, sizeof(connection_url), "connect://%s", final_host_and_port.c_str ());
|
||||
|
||||
// Create the connection.
|
||||
std::unique_ptr<ConnectionFileDescriptor> connection_up (new ConnectionFileDescriptor ());
|
||||
connection_up.reset (new ConnectionFileDescriptor ());
|
||||
auto connection_result = connection_up->Connect (connection_url, &error);
|
||||
if (connection_result != eConnectionStatusSuccess)
|
||||
{
|
||||
fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path);
|
||||
fprintf (stderr, "error: failed to connect to client at '%s' (connection status: %d)", connection_url, static_cast<int> (connection_result));
|
||||
exit (-1);
|
||||
}
|
||||
if (error.Fail ())
|
||||
{
|
||||
fprintf (stderr, "error: failed to connect to client at '%s': %s", connection_url, error.AsCString ());
|
||||
exit (-1);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the listener thread.
|
||||
if (!JoinListenThread ())
|
||||
{
|
||||
fprintf (stderr, "failed to join the listener thread\n");
|
||||
display_usage (progname);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
// Ensure we connected.
|
||||
if (s_listen_connection_up)
|
||||
{
|
||||
// We're connected.
|
||||
printf ("Connection established.\n");
|
||||
gdb_server.SetConnection (s_listen_connection_up.release());
|
||||
gdb_server.SetConnection (connection_up.release());
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "failed to connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ());
|
||||
display_usage (progname);
|
||||
exit (1);
|
||||
// llgs will listen for connections on the given port from the given address.
|
||||
// Start the listener on a new thread. We need to do this so we can resolve the
|
||||
// bound listener port.
|
||||
StartListenThread(connection_host.c_str (), static_cast<uint16_t> (connection_portno));
|
||||
printf ("Listening to port %s for a connection from %s...\n", connection_port.c_str (), connection_host.c_str ());
|
||||
|
||||
// If we have a named pipe to write the port number back to, do that now.
|
||||
if (named_pipe_path && named_pipe_path[0] && connection_portno == 0)
|
||||
{
|
||||
// FIXME use new generic named pipe support.
|
||||
int fd = ::open(named_pipe_path, O_WRONLY);
|
||||
if (fd > -1)
|
||||
{
|
||||
const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10);
|
||||
|
||||
char port_str[64];
|
||||
const ssize_t port_str_len = ::snprintf (port_str, sizeof(port_str), "%u", bound_port);
|
||||
// Write the port number as a C string with the NULL terminator.
|
||||
::write (fd, port_str, port_str_len + 1);
|
||||
close (fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the listener thread.
|
||||
if (!JoinListenThread ())
|
||||
{
|
||||
fprintf (stderr, "failed to join the listener thread\n");
|
||||
display_usage (progname);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
// Ensure we connected.
|
||||
if (s_listen_connection_up)
|
||||
{
|
||||
printf ("Connection established.\n");
|
||||
gdb_server.SetConnection (s_listen_connection_up.release());
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "failed to connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ());
|
||||
display_usage (progname);
|
||||
exit (1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -411,7 +451,7 @@ start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host
|
|||
|
||||
bool interrupt = false;
|
||||
bool done = false;
|
||||
while (!interrupt && !done && (g_sighup_received_count < 1))
|
||||
while (!interrupt && !done && (g_sighup_received_count < 2))
|
||||
{
|
||||
const GDBRemoteCommunication::PacketResult result = gdb_server.GetPacketAndSendResponse (TIMEOUT_USEC, error, interrupt, done);
|
||||
if ((result != GDBRemoteCommunication::PacketResult::Success) &&
|
||||
|
@ -462,6 +502,7 @@ main (int argc, char *argv[])
|
|||
std::string platform_name;
|
||||
std::string attach_target;
|
||||
std::string named_pipe_path;
|
||||
bool reverse_connect = false;
|
||||
|
||||
initialize_lldb_gdbserver ();
|
||||
|
||||
|
@ -547,6 +588,10 @@ main (int argc, char *argv[])
|
|||
// Do nothing, native regs is the default these days
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
reverse_connect = true;
|
||||
break;
|
||||
|
||||
#ifndef _WIN32
|
||||
case 'S':
|
||||
// Put llgs into a new session. Terminals group processes
|
||||
|
@ -631,7 +676,7 @@ main (int argc, char *argv[])
|
|||
// Print version info.
|
||||
printf("%s-%s", LLGS_PROGRAM_NAME, LLGS_VERSION_STR);
|
||||
|
||||
start_listener (gdb_server, host_and_port, progname, named_pipe_path.c_str ());
|
||||
ConnectToRemote (gdb_server, reverse_connect, host_and_port, progname, named_pipe_path.c_str ());
|
||||
|
||||
terminate_lldb_gdbserver ();
|
||||
|
||||
|
|
Loading…
Reference in New Issue