Added llgs --named-pipe support and program_name-version_number printout support.

Added a unit test to test debugserver and llgs compliance on --named-pipe support.

Modified llgs to implement --named-pipe support.  (Note: need to revisit with
new generic pipe support).

llvm-svn: 212854
This commit is contained in:
Todd Fiala 2014-07-11 22:50:13 +00:00
parent a39390699c
commit 67041194b9
3 changed files with 252 additions and 52 deletions

View File

@ -0,0 +1,87 @@
import unittest2
import gdbremote_testcase
import os
import os.path
import select
import tempfile
import time
from lldbtest import *
class TestStubNamedPipe(gdbremote_testcase.GdbRemoteTestCaseBase):
def create_named_pipe(self):
temp_dir = tempfile.mkdtemp()
named_pipe_path = os.path.join(temp_dir, "stub_port_number")
try:
os.mkfifo(named_pipe_path)
except OSError, e:
# print "Failed to create named pipe: %s" % e
raise e
return named_pipe_path
def get_port_from_named_pipe(self):
# Set port to 0
self.port = 0
# Don't turn on any kind of logging
self.debug_monitor_extra_args = ""
# Create the named pipe that we're reading on.
self.named_pipe_path = self.create_named_pipe()
self.assertIsNotNone(self.named_pipe_path)
# print "using named pipe:{}".format(self.named_pipe_path)
try:
# print "launching server..."
server = self.launch_debug_monitor()
# print "server launched..."
self.assertIsNotNone(server)
self.assertTrue(server.isalive())
server.expect("(debugserver|lldb-gdbserver)", timeout=10)
# print "about to open named pipe..."
# Open the read side of the pipe in non-blocking mode. This will return right away, ready or not.
fd = os.open(self.named_pipe_path, os.O_RDONLY | os.O_NONBLOCK)
named_pipe = os.fdopen(fd, "r")
self.assertIsNotNone(named_pipe)
# print "waiting on content from the named pipe..."
# Wait for something to read with a max timeout.
(ready_readers, _, _) = select.select([fd], [], [], 5)
self.assertIsNotNone(ready_readers, "write side of pipe has not written anything - stub isn't writing to pipe.")
self.assertNotEqual(len(ready_readers), 0, "write side of pipe has not written anything - stub isn't writing to pipe.")
try:
# Read the port from the named pipe.
stub_port_raw = named_pipe.read()
self.assertIsNotNone(stub_port_raw)
self.assertNotEqual(len(stub_port_raw), 0, "no content to read on pipe")
# Trim null byte, convert to int.
stub_port_raw = stub_port_raw[:-1]
stub_port = int(stub_port_raw)
self.assertTrue(stub_port > 0)
finally:
named_pipe.close()
# print "stub is listening on port: {} (from text '{}')".format(stub_port, stub_port_raw)
finally:
temp_dir = os.path.dirname(self.named_pipe_path)
try:
os.remove(self.named_pipe_path)
except:
# Not required.
None
os.rmdir(temp_dir)
@debugserver_test
def test_get_port_from_named_pipe_debugserver(self):
self.init_debugserver_test()
self.set_inferior_startup_launch()
self.get_port_from_named_pipe()
@llgs_test
@dwarf_test
# @unittest2.expectedFailure()
def test_get_port_from_named_pipe_llgs(self):
self.init_llgs_test()
self.set_inferior_startup_launch()
self.get_port_from_named_pipe()

View File

@ -50,6 +50,7 @@ class GdbRemoteTestCaseBase(TestBase):
self.test_sequence = GdbRemoteTestSequence(self.logger)
self.set_inferior_startup_launch()
self.port = self.get_next_port()
self.named_pipe_path = None
def get_next_port(self):
return 12000 + random.randint(0,3999)
@ -102,6 +103,8 @@ class GdbRemoteTestCaseBase(TestBase):
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
# Start the server.
server = pexpect.spawn(commandline)

View File

@ -34,9 +34,26 @@
#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServer.h"
#include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h"
#ifndef LLGS_PROGRAM_NAME
#define LLGS_PROGRAM_NAME "lldb-gdbserver"
#endif
#ifndef LLGS_VERSION_STR
#define LLGS_VERSION_STR "local_build"
#endif
using namespace lldb;
using namespace lldb_private;
// lldb-gdbserver state
namespace
{
static lldb::tid_t s_listen_thread = LLDB_INVALID_HOST_THREAD;
static std::unique_ptr<ConnectionFileDescriptor> s_listen_connection_up;
static std::string s_listen_url;
}
//----------------------------------------------------------------------
// option descriptors for getopt_long_only()
//----------------------------------------------------------------------
@ -53,6 +70,7 @@ static struct option g_long_options[] =
{ "log-file", required_argument, NULL, 'l' },
{ "log-flags", required_argument, NULL, 'f' },
{ "attach", required_argument, NULL, 'a' },
{ "named-pipe", required_argument, NULL, 'P' },
{ NULL, 0, NULL, 0 }
};
@ -254,82 +272,165 @@ handle_launch (GDBRemoteCommunicationServer &gdb_server, int argc, const char *c
}
}
static lldb::thread_result_t
ListenThread (lldb::thread_arg_t /* arg */)
{
Error error;
if (s_listen_connection_up)
{
// Do the listen on another thread so we can continue on...
if (s_listen_connection_up->Connect(s_listen_url.c_str(), &error) != eConnectionStatusSuccess)
s_listen_connection_up.reset();
}
return nullptr;
}
static Error
StartListenThread (const char *hostname, uint16_t port)
{
Error error;
if (IS_VALID_LLDB_HOST_THREAD(s_listen_thread))
{
error.SetErrorString("listen thread already running");
}
else
{
char listen_url[512];
if (hostname && hostname[0])
snprintf(listen_url, sizeof(listen_url), "listen://%s:%i", hostname, port);
else
snprintf(listen_url, sizeof(listen_url), "listen://%i", port);
s_listen_url = listen_url;
s_listen_connection_up.reset (new ConnectionFileDescriptor ());
s_listen_thread = Host::ThreadCreate (listen_url, ListenThread, nullptr, &error);
}
return error;
}
static bool
JoinListenThread ()
{
if (IS_VALID_LLDB_HOST_THREAD(s_listen_thread))
{
Host::ThreadJoin(s_listen_thread, nullptr, nullptr);
s_listen_thread = LLDB_INVALID_HOST_THREAD;
}
return true;
}
void
start_listener (GDBRemoteCommunicationServer &gdb_server, const char *const host_and_port, const char *const progname)
start_listener (GDBRemoteCommunicationServer &gdb_server, 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])
{
std::unique_ptr<ConnectionFileDescriptor> conn_ap(new ConnectionFileDescriptor());
if (conn_ap.get())
std::string final_host_and_port;
std::string listening_host;
std::string listening_port;
uint32_t listening_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] == ':')
final_host_and_port.append ("localhost");
final_host_and_port.append (host_and_port);
const std::string::size_type colon_pos = final_host_and_port.find (':');
if (colon_pos != std::string::npos)
{
std::string final_host_and_port;
std::string listening_host;
std::string listening_port;
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);
}
else
{
fprintf (stderr, "failed to parse host and port from connection string '%s'\n", final_host_and_port.c_str ());
display_usage (progname);
exit (1);
}
// 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] == ':')
final_host_and_port.append ("localhost");
final_host_and_port.append (host_and_port);
// 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 ());
const std::string::size_type colon_pos = final_host_and_port.find(':');
if (colon_pos != std::string::npos)
// 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)
{
// FIXME use new generic named pipe support.
int fd = ::open(named_pipe_path, O_WRONLY);
if (fd > -1)
{
listening_host = final_host_and_port.substr(0, colon_pos);
listening_port = final_host_and_port.substr(colon_pos + 1);
}
const uint16_t bound_port = s_listen_connection_up->GetBoundPort (10);
std::string connect_url ("listen://");
connect_url.append (final_host_and_port);
printf ("Listening to port %s for a connection from %s...\n", listening_port.c_str (), listening_host.c_str ());
if (conn_ap->Connect(connect_url.c_str(), &error) == eConnectionStatusSuccess)
{
printf ("Connection established.\n");
gdb_server.SetConnection (conn_ap.release());
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 connect to '%s': %s\n", final_host_and_port.c_str (), error.AsCString ());
display_usage (progname);
exit (1);
fprintf (stderr, "failed to open named pipe '%s' for writing\n", named_pipe_path);
}
}
if (gdb_server.IsConnected())
// Join the listener thread.
if (!JoinListenThread ())
{
// After we connected, we need to get an initial ack from...
if (gdb_server.HandshakeWithClient(&error))
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);
}
}
if (gdb_server.IsConnected())
{
// After we connected, we need to get an initial ack from...
if (gdb_server.HandshakeWithClient(&error))
{
// We'll use a half a second timeout interval so that an exit conditions can
// be checked that often.
const uint32_t TIMEOUT_USEC = 500000;
bool interrupt = false;
bool done = false;
while (!interrupt && !done && (g_sighup_received_count < 1))
{
// We'll use a half a second timeout interval so that an exit conditions can
// be checked that often.
const uint32_t TIMEOUT_USEC = 500000;
bool interrupt = false;
bool done = false;
while (!interrupt && !done && (g_sighup_received_count < 1))
const GDBRemoteCommunication::PacketResult result = gdb_server.GetPacketAndSendResponse (TIMEOUT_USEC, error, interrupt, done);
if ((result != GDBRemoteCommunication::PacketResult::Success) &&
(result != GDBRemoteCommunication::PacketResult::ErrorReplyTimeout))
{
const GDBRemoteCommunication::PacketResult result = gdb_server.GetPacketAndSendResponse (TIMEOUT_USEC, error, interrupt, done);
if ((result != GDBRemoteCommunication::PacketResult::Success) &&
(result != GDBRemoteCommunication::PacketResult::ErrorReplyTimeout))
{
// We're bailing out - we only support successful handling and timeouts.
fprintf(stderr, "leaving packet loop due to PacketResult %d\n", result);
break;
}
}
if (error.Fail())
{
fprintf(stderr, "error: %s\n", error.AsCString());
// We're bailing out - we only support successful handling and timeouts.
fprintf(stderr, "leaving packet loop due to PacketResult %d\n", result);
break;
}
}
else
if (error.Fail())
{
fprintf(stderr, "error: handshake with client failed\n");
fprintf(stderr, "error: %s\n", error.AsCString());
}
}
else
{
fprintf(stderr, "error: handshake with client failed\n");
}
}
else
{
@ -357,6 +458,7 @@ main (int argc, char *argv[])
int ch;
std::string platform_name;
std::string attach_target;
std::string named_pipe_path;
initialize_lldb_gdbserver ();
@ -433,6 +535,11 @@ main (int argc, char *argv[])
platform_name = optarg;
break;
case 'P': // named pipe
if (optarg && optarg[0])
named_pipe_path = optarg;
break;
case 'a': // attach {pid|process_name}
if (optarg && optarg[0])
attach_target = optarg;
@ -490,7 +597,10 @@ main (int argc, char *argv[])
else if (argc > 0)
handle_launch (gdb_server, argc, argv);
start_listener (gdb_server, host_and_port, progname);
// 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 ());
terminate_lldb_gdbserver ();