forked from OSchip/llvm-project
560 lines
19 KiB
C++
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;
|
|
}
|