llgs: add --reverse-connect support.

Also includes --reverse-connect tests for llgs and debugserver.

llvm-svn: 214031
This commit is contained in:
Todd Fiala 2014-07-26 20:39:17 +00:00
parent 7b70cadae9
commit 31bde322f3
4 changed files with 228 additions and 53 deletions

View File

@ -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()

View File

@ -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: +",

View File

@ -6,7 +6,6 @@ import os.path
import platform
import Queue
import re
import select
import socket_packet_pump
import subprocess
import time

View File

@ -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 ();