llvm-project/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServe...

560 lines
19 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"
// 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)
{
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qC,
&GDBRemoteCommunicationServerPlatform::Handle_qC);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qGetWorkingDir,
&GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir);
RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_qLaunchGDBServer,
&GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer);
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()
{
}
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__);
// Sleep and wait a bit for debugserver to start to listen...
ConnectionFileDescriptor file_conn;
std::string hostname;
// TODO: /tmp/ should not be hardcoded. User might want to override /tmp
// with the TMPDIR environment variable
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);
}
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";
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::string socket_name;
std::ostringstream url;
uint16_t* port_ptr = &port;
url << m_socket_scheme << "://";
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);
lldb::pid_t debugserver_pid = debugserver_launch_info.GetProcessID();
if (debugserver_pid != LLDB_INVALID_PROCESS_ID)
{
Mutex::Locker locker (m_spawned_pids_mutex);
m_spawned_pids.insert(debugserver_pid);
if (port > 0)
AssociatePortWithProcess(port, debugserver_pid);
}
else
{
if (port > 0)
FreePort (port);
}
if (error.Success())
{
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;
}
else
{
if (log)
log->Printf ("GDBRemoteCommunicationServerPlatform::%s() debugserver launch failed: %s", __FUNCTION__, error.AsCString ());
}
return SendErrorResponse (9);
#endif
}
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;
}