llvm-project/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp
Tamas Berghammer ccd6cffba3 Modify "platform connect" to connect to processes as well
The standard remote debugging workflow with gdb is to start the
application on the remote host under gdbserver (e.g.: gdbserver :5039
a.out) and then connect to it with gdb.

The same workflow is supported by debugserver/lldb-gdbserver with a very
similar syntax but to access all features of lldb we need to be
connected also to an lldb-platform instance running on the target.

Before this change this had to be done manually with starting a separate
lldb-platform on the target machine and then connecting to it with lldb
before connecting to the process.

This change modifies the behavior of "platform connect" with
automatically connecting to the process instance if it was started by
the remote platform. With this command replacing gdbserver in a gdb
based worflow is usually as simple as replacing the command to execute
gdbserver with executing lldb-platform.

Differential revision: http://reviews.llvm.org/D14952

llvm-svn: 255016
2015-12-08 14:08:19 +00:00

603 lines
21 KiB
C++

//===-- GDBRemoteCommunicationServerPlatform.cpp ----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "GDBRemoteCommunicationServerPlatform.h"
#include <errno.h>
// C Includes
// C++ Includes
#include <cstring>
#include <chrono>
#include <mutex>
#include <sstream>
// Other libraries and framework includes
#include "llvm/Support/FileSystem.h"
#include "lldb/Core/Log.h"
#include "lldb/Core/StreamGDBRemote.h"
#include "lldb/Core/StreamString.h"
#include "lldb/Core/StructuredData.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/StringConvert.h"
#include "lldb/Target/FileAction.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Utility/JSON.h"
// Project includes
#include "Utility/StringExtractorGDBRemote.h"
#include "Utility/UriParser.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::process_gdb_remote;
//----------------------------------------------------------------------
// GDBRemoteCommunicationServerPlatform constructor
//----------------------------------------------------------------------
GDBRemoteCommunicationServerPlatform::GDBRemoteCommunicationServerPlatform(const Socket::SocketProtocol socket_protocol,
const char* socket_scheme) :
GDBRemoteCommunicationServerCommon ("gdb-remote.server", "gdb-remote.server.rx_packet"),
m_socket_protocol(socket_protocol),
m_socket_scheme(socket_scheme),
m_spawned_pids_mutex (Mutex::eMutexTypeRecursive),
m_platform_sp (Platform::GetHostPlatform ()),
m_port_map (),
m_port_offset(0),
m_pending_gdb_server{ LLDB_INVALID_PROCESS_ID, 0, "" }
{
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qC,
&GDBRemoteCommunicationServerPlatform::Handle_qC);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qGetWorkingDir,
&GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qLaunchGDBServer,
&GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qQueryGDBServer,
&GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qKillSpawnedProcess,
&GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qProcessInfo,
&GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_QSetWorkingDir,
&GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_jSignalsInfo,
&GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo);
RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_interrupt,
[this](StringExtractorGDBRemote packet,
Error &error,
bool &interrupt,
bool &quit)
{
error.SetErrorString("interrupt received");
interrupt = true;
return PacketResult::Success;
});
}
//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
GDBRemoteCommunicationServerPlatform::~GDBRemoteCommunicationServerPlatform()
{
}
Error
GDBRemoteCommunicationServerPlatform::LaunchGDBServer(const lldb_private::Args& args,
std::string hostname,
lldb::pid_t& pid,
uint16_t& port,
std::string& socket_name)
{
if (port == UINT16_MAX)
port = GetNextAvailablePort();
// Spawn a new thread to accept the port that gets bound after
// binding to port 0 (zero).
// ignore the hostname send from the remote end, just use the ip address
// that we're currently communicating with as the hostname
// Spawn a debugserver and try to get the port it listens to.
ProcessLaunchInfo debugserver_launch_info;
if (hostname.empty())
hostname = "127.0.0.1";
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
if (log)
log->Printf("Launching debugserver with: %s:%u...", hostname.c_str(), port);
// Do not run in a new session so that it can not linger after the
// platform closes.
debugserver_launch_info.SetLaunchInSeparateProcessGroup(false);
debugserver_launch_info.SetMonitorProcessCallback(ReapDebugserverProcess, this, false);
std::string platform_scheme;
std::string platform_ip;
int platform_port;
std::string platform_path;
bool ok = UriParser::Parse(GetConnection()->GetURI().c_str(), platform_scheme, platform_ip, platform_port, platform_path);
UNUSED_IF_ASSERT_DISABLED(ok);
assert(ok);
std::ostringstream url;
uint16_t* port_ptr = &port;
if (m_socket_protocol == Socket::ProtocolTcp)
url << platform_ip << ":" << port;
else
{
socket_name = GetDomainSocketPath("gdbserver").GetPath();
url << socket_name;
port_ptr = nullptr;
}
Error error = StartDebugserverProcess (url.str().c_str(),
nullptr,
debugserver_launch_info,
port_ptr,
args);
pid = debugserver_launch_info.GetProcessID();
if (pid != LLDB_INVALID_PROCESS_ID)
{
Mutex::Locker locker (m_spawned_pids_mutex);
m_spawned_pids.insert(pid);
if (port > 0)
AssociatePortWithProcess(port, pid);
}
else
{
if (port > 0)
FreePort(port);
}
return error;
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer (StringExtractorGDBRemote &packet)
{
#ifdef _WIN32
return SendErrorResponse(9);
#else
// Spawn a local debugserver as a platform so we can then attach or launch
// a process...
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM));
if (log)
log->Printf ("GDBRemoteCommunicationServerPlatform::%s() called", __FUNCTION__);
ConnectionFileDescriptor file_conn;
std::string hostname;
packet.SetFilePos(::strlen ("qLaunchGDBServer;"));
std::string name;
std::string value;
uint16_t port = UINT16_MAX;
while (packet.GetNameColonValue(name, value))
{
if (name.compare ("host") == 0)
hostname.swap(value);
else if (name.compare ("port") == 0)
port = StringConvert::ToUInt32(value.c_str(), 0, 0);
}
lldb::pid_t debugserver_pid = LLDB_INVALID_PROCESS_ID;
std::string socket_name;
Error error = LaunchGDBServer(Args(), hostname, debugserver_pid, port, socket_name);
if (error.Fail())
{
if (log)
log->Printf("GDBRemoteCommunicationServerPlatform::%s() debugserver launch failed: %s", __FUNCTION__, error.AsCString ());
return SendErrorResponse(9);
}
if (log)
log->Printf ("GDBRemoteCommunicationServerPlatform::%s() debugserver launched successfully as pid %" PRIu64, __FUNCTION__, debugserver_pid);
StreamGDBRemote response;
response.Printf("pid:%" PRIu64 ";port:%u;", debugserver_pid, port + m_port_offset);
if (!socket_name.empty())
{
response.PutCString("socket_name:");
response.PutCStringAsRawHex8(socket_name.c_str());
response.PutChar(';');
}
PacketResult packet_result = SendPacketNoLock(response.GetData(), response.GetSize());
if (packet_result != PacketResult::Success)
{
if (debugserver_pid != LLDB_INVALID_PROCESS_ID)
::kill (debugserver_pid, SIGINT);
}
return packet_result;
#endif
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer (StringExtractorGDBRemote &packet)
{
if (m_pending_gdb_server.pid == LLDB_INVALID_PROCESS_ID)
return SendErrorResponse(4);
JSONObject::SP server_sp = std::make_shared<JSONObject>();
server_sp->SetObject("port", std::make_shared<JSONNumber>(m_pending_gdb_server.port));
if (!m_pending_gdb_server.socket_name.empty())
server_sp->SetObject("socket_name",
std::make_shared<JSONString>(m_pending_gdb_server.socket_name.c_str()));
JSONArray server_list;
server_list.AppendObject(server_sp);
StreamGDBRemote response;
server_list.Write(response);
StreamGDBRemote escaped_response;
escaped_response.PutEscapedBytes(response.GetData(), response.GetSize());
return SendPacketNoLock(escaped_response.GetData(), escaped_response.GetSize());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qKillSpawnedProcess (StringExtractorGDBRemote &packet)
{
packet.SetFilePos(::strlen ("qKillSpawnedProcess:"));
lldb::pid_t pid = packet.GetU64(LLDB_INVALID_PROCESS_ID);
// verify that we know anything about this pid.
// Scope for locker
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
{
// not a pid we know about
return SendErrorResponse (10);
}
}
// go ahead and attempt to kill the spawned process
if (KillSpawnedProcess (pid))
return SendOKResponse ();
else
return SendErrorResponse (11);
}
bool
GDBRemoteCommunicationServerPlatform::KillSpawnedProcess (lldb::pid_t pid)
{
// make sure we know about this process
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
return false;
}
// first try a SIGTERM (standard kill)
Host::Kill (pid, SIGTERM);
// check if that worked
for (size_t i=0; i<10; ++i)
{
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
{
// it is now killed
return true;
}
}
usleep (10000);
}
// check one more time after the final usleep
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
return true;
}
// the launched process still lives. Now try killing it again,
// this time with an unblockable signal.
Host::Kill (pid, SIGKILL);
for (size_t i=0; i<10; ++i)
{
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
{
// it is now killed
return true;
}
}
usleep (10000);
}
// check one more time after the final usleep
// Scope for locker
{
Mutex::Locker locker (m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end())
return true;
}
// no luck - the process still lives
return false;
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qProcessInfo (StringExtractorGDBRemote &packet)
{
lldb::pid_t pid = m_process_launch_info.GetProcessID ();
m_process_launch_info.Clear ();
if (pid == LLDB_INVALID_PROCESS_ID)
return SendErrorResponse (1);
ProcessInstanceInfo proc_info;
if (!Host::GetProcessInfo (pid, proc_info))
return SendErrorResponse (1);
StreamString response;
CreateProcessInfoResponse_DebugServerStyle(proc_info, response);
return SendPacketNoLock (response.GetData (), response.GetSize ());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir (StringExtractorGDBRemote &packet)
{
// If this packet is sent to a platform, then change the current working directory
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof(cwd)) == NULL)
return SendErrorResponse(errno);
StreamString response;
response.PutBytesAsRawHex8(cwd, strlen(cwd));
return SendPacketNoLock(response.GetData(), response.GetSize());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir (StringExtractorGDBRemote &packet)
{
packet.SetFilePos (::strlen ("QSetWorkingDir:"));
std::string path;
packet.GetHexByteString (path);
// If this packet is sent to a platform, then change the current working directory
if (::chdir(path.c_str()) != 0)
return SendErrorResponse (errno);
return SendOKResponse ();
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qC (StringExtractorGDBRemote &packet)
{
// NOTE: lldb should now be using qProcessInfo for process IDs. This path here
// should not be used. It is reporting process id instead of thread id. The
// correct answer doesn't seem to make much sense for lldb-platform.
// CONSIDER: flip to "unsupported".
lldb::pid_t pid = m_process_launch_info.GetProcessID();
StreamString response;
response.Printf("QC%" PRIx64, pid);
// If we launch a process and this GDB server is acting as a platform,
// then we need to clear the process launch state so we can start
// launching another process. In order to launch a process a bunch or
// packets need to be sent: environment packets, working directory,
// disable ASLR, and many more settings. When we launch a process we
// then need to know when to clear this information. Currently we are
// selecting the 'qC' packet as that packet which seems to make the most
// sense.
if (pid != LLDB_INVALID_PROCESS_ID)
{
m_process_launch_info.Clear();
}
return SendPacketNoLock (response.GetData(), response.GetSize());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo(StringExtractorGDBRemote &packet)
{
StructuredData::Array signal_array;
const auto &signals = Host::GetUnixSignals();
for (auto signo = signals->GetFirstSignalNumber();
signo != LLDB_INVALID_SIGNAL_NUMBER;
signo = signals->GetNextSignalNumber(signo))
{
auto dictionary = std::make_shared<StructuredData::Dictionary>();
dictionary->AddIntegerItem("signo", signo);
dictionary->AddStringItem("name", signals->GetSignalAsCString(signo));
bool suppress, stop, notify;
signals->GetSignalInfo(signo, suppress, stop, notify);
dictionary->AddBooleanItem("suppress", suppress);
dictionary->AddBooleanItem("stop", stop);
dictionary->AddBooleanItem("notify", notify);
signal_array.Push(dictionary);
}
StreamString response;
signal_array.Dump(response);
return SendPacketNoLock(response.GetData(), response.GetSize());
}
bool
GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped (lldb::pid_t pid)
{
Mutex::Locker locker (m_spawned_pids_mutex);
FreePortForProcess(pid);
return m_spawned_pids.erase(pid) > 0;
}
bool
GDBRemoteCommunicationServerPlatform::ReapDebugserverProcess (void *callback_baton,
lldb::pid_t pid,
bool exited,
int signal, // Zero for no signal
int status) // Exit value of process if signal is zero
{
GDBRemoteCommunicationServerPlatform *server = (GDBRemoteCommunicationServerPlatform *)callback_baton;
server->DebugserverProcessReaped (pid);
return true;
}
Error
GDBRemoteCommunicationServerPlatform::LaunchProcess ()
{
if (!m_process_launch_info.GetArguments ().GetArgumentCount ())
return Error ("%s: no process command line specified to launch", __FUNCTION__);
// specify the process monitor if not already set. This should
// generally be what happens since we need to reap started
// processes.
if (!m_process_launch_info.GetMonitorProcessCallback ())
m_process_launch_info.SetMonitorProcessCallback(ReapDebugserverProcess, this, false);
Error error = m_platform_sp->LaunchProcess (m_process_launch_info);
if (!error.Success ())
{
fprintf (stderr, "%s: failed to launch executable %s", __FUNCTION__, m_process_launch_info.GetArguments ().GetArgumentAtIndex (0));
return error;
}
printf ("Launched '%s' as process %" PRIu64 "...\n", m_process_launch_info.GetArguments ().GetArgumentAtIndex (0), m_process_launch_info.GetProcessID());
// add to list of spawned processes. On an lldb-gdbserver, we
// would expect there to be only one.
const auto pid = m_process_launch_info.GetProcessID();
if (pid != LLDB_INVALID_PROCESS_ID)
{
// add to spawned pids
Mutex::Locker locker (m_spawned_pids_mutex);
m_spawned_pids.insert(pid);
}
return error;
}
void
GDBRemoteCommunicationServerPlatform::SetPortMap (PortMap &&port_map)
{
m_port_map = port_map;
}
uint16_t
GDBRemoteCommunicationServerPlatform::GetNextAvailablePort ()
{
if (m_port_map.empty())
return 0; // Bind to port zero and get a port, we didn't have any limitations
for (auto &pair : m_port_map)
{
if (pair.second == LLDB_INVALID_PROCESS_ID)
{
pair.second = ~(lldb::pid_t)LLDB_INVALID_PROCESS_ID;
return pair.first;
}
}
return UINT16_MAX;
}
bool
GDBRemoteCommunicationServerPlatform::AssociatePortWithProcess (uint16_t port, lldb::pid_t pid)
{
PortMap::iterator pos = m_port_map.find(port);
if (pos != m_port_map.end())
{
pos->second = pid;
return true;
}
return false;
}
bool
GDBRemoteCommunicationServerPlatform::FreePort (uint16_t port)
{
PortMap::iterator pos = m_port_map.find(port);
if (pos != m_port_map.end())
{
pos->second = LLDB_INVALID_PROCESS_ID;
return true;
}
return false;
}
bool
GDBRemoteCommunicationServerPlatform::FreePortForProcess (lldb::pid_t pid)
{
if (!m_port_map.empty())
{
for (auto &pair : m_port_map)
{
if (pair.second == pid)
{
pair.second = LLDB_INVALID_PROCESS_ID;
return true;
}
}
}
return false;
}
const FileSpec&
GDBRemoteCommunicationServerPlatform::GetDomainSocketDir()
{
static FileSpec g_domainsocket_dir;
static std::once_flag g_once_flag;
std::call_once(g_once_flag, []() {
const char* domainsocket_dir_env = ::getenv("LLDB_DEBUGSERVER_DOMAINSOCKET_DIR");
if (domainsocket_dir_env != nullptr)
g_domainsocket_dir = FileSpec(domainsocket_dir_env, false);
else
HostInfo::GetLLDBPath(ePathTypeLLDBTempSystemDir, g_domainsocket_dir);
});
return g_domainsocket_dir;
}
FileSpec
GDBRemoteCommunicationServerPlatform::GetDomainSocketPath(const char* prefix)
{
llvm::SmallString<PATH_MAX> socket_path;
llvm::SmallString<PATH_MAX> socket_name((llvm::StringRef(prefix) + ".%%%%%%").str());
FileSpec socket_path_spec(GetDomainSocketDir());
socket_path_spec.AppendPathComponent(socket_name.c_str());
llvm::sys::fs::createUniqueFile(socket_path_spec.GetCString(), socket_path);
return FileSpec(socket_path.c_str(), false);
}
void
GDBRemoteCommunicationServerPlatform::SetPortOffset(uint16_t port_offset)
{
m_port_offset = port_offset;
}
void
GDBRemoteCommunicationServerPlatform::SetPendingGdbServer(lldb::pid_t pid,
uint16_t port,
const std::string& socket_name)
{
m_pending_gdb_server.pid = pid;
m_pending_gdb_server.port = port;
m_pending_gdb_server.socket_name = socket_name;
}