llvm-project/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.cpp
Greg Clayton 529a3d87a7 [NFC] Improve FileSpec internal APIs and usage in preparation for adding caching of resolved/absolute.
Resubmission of https://reviews.llvm.org/D130309 with the 2 patches that fixed the linux buildbot, and new windows fixes.

The FileSpec APIs allow users to modify instance variables directly by getting a non const reference to the directory and filename instance variables. This makes it impossible to control all of the times the FileSpec object is modified so we can clear cached member variables like m_resolved and with an upcoming patch caching if the file is relative or absolute. This patch modifies the APIs of FileSpec so no one can modify the directory or filename instance variables directly by adding set accessors and by removing the get accessors that are non const.

Many clients were using FileSpec::GetCString(...) which returned a unique C string from a ConstString'ified version of the result of GetPath() which returned a std::string. This caused many locations to use this convenient function incorrectly and could cause many strings to be added to the constant string pool that didn't need to. Most clients were converted to using FileSpec::GetPath().c_str() when possible. Other clients were modified to use the newly renamed version of this function which returns an actualy ConstString:

ConstString FileSpec::GetPathAsConstString(bool denormalize = true) const;

This avoids the issue where people were getting an already uniqued "const char *" that came from a ConstString only to put the "const char *" back into a "ConstString" object. By returning the ConstString instead of a "const char *" clients can be more efficient with the result.

The patch:
- Removes the non const GetDirectory() and GetFilename() get accessors
- Adds set accessors to replace the above functions: SetDirectory() and SetFilename().
- Adds ClearDirectory() and ClearFilename() to replace usage of the FileSpec::GetDirectory().Clear()/FileSpec::GetFilename().Clear() call sites
- Fixed all incorrect usage of FileSpec::GetCString() to use FileSpec::GetPath().c_str() where appropriate, and updated other call sites that wanted a ConstString to use the newly returned ConstString appropriately and efficiently.

Differential Revision: https://reviews.llvm.org/D130549
2022-07-28 13:28:26 -07:00

605 lines
20 KiB
C++

//===-- GDBRemoteCommunicationServerPlatform.cpp --------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "GDBRemoteCommunicationServerPlatform.h"
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstring>
#include <mutex>
#include <sstream>
#include <thread>
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/ConnectionFileDescriptor.h"
#include "lldb/Host/FileAction.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Interpreter/CommandCompletions.h"
#include "lldb/Target/Platform.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Utility/GDBRemote.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/TildeExpressionResolver.h"
#include "lldb/Utility/UriParser.h"
#include "lldb/Utility/StringExtractorGDBRemote.h"
using namespace lldb;
using namespace lldb_private::process_gdb_remote;
using namespace lldb_private;
GDBRemoteCommunicationServerPlatform::PortMap::PortMap(uint16_t min_port,
uint16_t max_port) {
for (; min_port < max_port; ++min_port)
m_port_map[min_port] = LLDB_INVALID_PROCESS_ID;
}
void GDBRemoteCommunicationServerPlatform::PortMap::AllowPort(uint16_t port) {
// Do not modify existing mappings
m_port_map.insert({port, LLDB_INVALID_PROCESS_ID});
}
llvm::Expected<uint16_t>
GDBRemoteCommunicationServerPlatform::PortMap::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 llvm::createStringError(llvm::inconvertibleErrorCode(),
"No free port found in port map");
}
bool GDBRemoteCommunicationServerPlatform::PortMap::AssociatePortWithProcess(
uint16_t port, lldb::pid_t pid) {
auto pos = m_port_map.find(port);
if (pos != m_port_map.end()) {
pos->second = pid;
return true;
}
return false;
}
bool GDBRemoteCommunicationServerPlatform::PortMap::FreePort(uint16_t port) {
std::map<uint16_t, lldb::pid_t>::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::PortMap::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;
}
bool GDBRemoteCommunicationServerPlatform::PortMap::empty() const {
return m_port_map.empty();
}
// 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(), m_port_map(), m_port_offset(0) {
m_pending_gdb_server.pid = LLDB_INVALID_PROCESS_ID;
m_pending_gdb_server.port = 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_qPathComplete,
&GDBRemoteCommunicationServerPlatform::Handle_qPathComplete);
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_QSetWorkingDir,
&GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir);
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_jSignalsInfo,
&GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo);
RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_interrupt,
[](StringExtractorGDBRemote packet, Status &error,
bool &interrupt, bool &quit) {
error.SetErrorString("interrupt received");
interrupt = true;
return PacketResult::Success;
});
}
// Destructor
GDBRemoteCommunicationServerPlatform::~GDBRemoteCommunicationServerPlatform() =
default;
Status GDBRemoteCommunicationServerPlatform::LaunchGDBServer(
const lldb_private::Args &args, std::string hostname, lldb::pid_t &pid,
llvm::Optional<uint16_t> &port, std::string &socket_name) {
if (!port) {
llvm::Expected<uint16_t> available_port = m_port_map.GetNextAvailablePort();
if (available_port)
port = *available_port;
else
return Status(available_port.takeError());
}
// 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 = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "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(
std::bind(&GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped,
this, std::placeholders::_1));
std::ostringstream url;
// debugserver does not accept the URL scheme prefix.
#if !defined(__APPLE__)
url << m_socket_scheme << "://";
#endif
uint16_t *port_ptr = port.getPointer();
if (m_socket_protocol == Socket::ProtocolTcp) {
std::string platform_uri = GetConnection()->GetURI();
llvm::Optional<URI> parsed_uri = URI::Parse(platform_uri);
url << '[' << parsed_uri->hostname.str() << "]:" << *port;
} else {
socket_name = GetDomainSocketPath("gdbserver").GetPath();
url << socket_name;
port_ptr = nullptr;
}
Status error = StartDebugserverProcess(
url.str().c_str(), nullptr, debugserver_launch_info, port_ptr, &args, -1);
pid = debugserver_launch_info.GetProcessID();
if (pid != LLDB_INVALID_PROCESS_ID) {
std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
m_spawned_pids.insert(pid);
if (*port > 0)
m_port_map.AssociatePortWithProcess(*port, pid);
} else {
if (*port > 0)
m_port_map.FreePort(*port);
}
return error;
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qLaunchGDBServer(
StringExtractorGDBRemote &packet) {
// Spawn a local debugserver as a platform so we can then attach or launch a
// process...
Log *log = GetLog(LLDBLog::Platform);
LLDB_LOGF(log, "GDBRemoteCommunicationServerPlatform::%s() called",
__FUNCTION__);
ConnectionFileDescriptor file_conn;
std::string hostname;
packet.SetFilePos(::strlen("qLaunchGDBServer;"));
llvm::StringRef name;
llvm::StringRef value;
llvm::Optional<uint16_t> port;
while (packet.GetNameColonValue(name, value)) {
if (name.equals("host"))
hostname = std::string(value);
else if (name.equals("port")) {
// Make the Optional valid so we can use its value
port = 0;
value.getAsInteger(0, *port);
}
}
lldb::pid_t debugserver_pid = LLDB_INVALID_PROCESS_ID;
std::string socket_name;
Status error =
LaunchGDBServer(Args(), hostname, debugserver_pid, port, socket_name);
if (error.Fail()) {
LLDB_LOGF(log,
"GDBRemoteCommunicationServerPlatform::%s() debugserver "
"launch failed: %s",
__FUNCTION__, error.AsCString());
return SendErrorResponse(9);
}
LLDB_LOGF(log,
"GDBRemoteCommunicationServerPlatform::%s() debugserver "
"launched successfully as pid %" PRIu64,
__FUNCTION__, debugserver_pid);
StreamGDBRemote response;
assert(port);
response.Printf("pid:%" PRIu64 ";port:%u;", debugserver_pid,
*port + m_port_offset);
if (!socket_name.empty()) {
response.PutCString("socket_name:");
response.PutStringAsRawHex8(socket_name);
response.PutChar(';');
}
PacketResult packet_result = SendPacketNoLock(response.GetString());
if (packet_result != PacketResult::Success) {
if (debugserver_pid != LLDB_INVALID_PROCESS_ID)
Host::Kill(debugserver_pid, SIGINT);
}
return packet_result;
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qQueryGDBServer(
StringExtractorGDBRemote &packet) {
namespace json = llvm::json;
if (m_pending_gdb_server.pid == LLDB_INVALID_PROCESS_ID)
return SendErrorResponse(4);
json::Object server{{"port", m_pending_gdb_server.port}};
if (!m_pending_gdb_server.socket_name.empty())
server.try_emplace("socket_name", m_pending_gdb_server.socket_name);
json::Array server_list;
server_list.push_back(std::move(server));
StreamGDBRemote response;
response.AsRawOstream() << std::move(server_list);
StreamGDBRemote escaped_response;
escaped_response.PutEscapedBytes(response.GetString().data(),
response.GetSize());
return SendPacketNoLock(escaped_response.GetString());
}
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
{
std::lock_guard<std::recursive_mutex> guard(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
{
std::lock_guard<std::recursive_mutex> guard(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) {
{
std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end()) {
// it is now killed
return true;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
{
std::lock_guard<std::recursive_mutex> guard(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) {
{
std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
if (m_spawned_pids.find(pid) == m_spawned_pids.end()) {
// it is now killed
return true;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// check one more time after the final sleep
{
std::lock_guard<std::recursive_mutex> guard(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.GetString());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qPathComplete(
StringExtractorGDBRemote &packet) {
packet.SetFilePos(::strlen("qPathComplete:"));
const bool only_dir = (packet.GetHexMaxU32(false, 0) == 1);
if (packet.GetChar() != ',')
return SendErrorResponse(85);
std::string path;
packet.GetHexByteString(path);
StringList matches;
StandardTildeExpressionResolver resolver;
if (only_dir)
CommandCompletions::DiskDirectories(path, matches, resolver);
else
CommandCompletions::DiskFiles(path, matches, resolver);
StreamString response;
response.PutChar('M');
llvm::StringRef separator;
std::sort(matches.begin(), matches.end());
for (const auto &match : matches) {
response << separator;
separator = ",";
// encode result strings into hex bytes to avoid unexpected error caused by
// special characters like '$'.
response.PutStringAsRawHex8(match.c_str());
}
return SendPacketNoLock(response.GetString());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_qGetWorkingDir(
StringExtractorGDBRemote &packet) {
llvm::SmallString<64> cwd;
if (std::error_code ec = llvm::sys::fs::current_path(cwd))
return SendErrorResponse(ec.value());
StreamString response;
response.PutBytesAsRawHex8(cwd.data(), cwd.size());
return SendPacketNoLock(response.GetString());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_QSetWorkingDir(
StringExtractorGDBRemote &packet) {
packet.SetFilePos(::strlen("QSetWorkingDir:"));
std::string path;
packet.GetHexByteString(path);
if (std::error_code ec = llvm::sys::fs::set_current_path(path))
return SendErrorResponse(ec.value());
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.GetString());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerPlatform::Handle_jSignalsInfo(
StringExtractorGDBRemote &packet) {
StructuredData::Array signal_array;
lldb::UnixSignalsSP signals = UnixSignals::CreateForHost();
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.GetString());
}
void GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped(
lldb::pid_t pid) {
std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
m_port_map.FreePortForProcess(pid);
m_spawned_pids.erase(pid);
}
Status GDBRemoteCommunicationServerPlatform::LaunchProcess() {
if (!m_process_launch_info.GetArguments().GetArgumentCount())
return Status("%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(std::bind(
&GDBRemoteCommunicationServerPlatform::DebugserverProcessReaped, this,
std::placeholders::_1));
Status error = Host::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
std::lock_guard<std::recursive_mutex> guard(m_spawned_pids_mutex);
m_spawned_pids.insert(pid);
}
return error;
}
void GDBRemoteCommunicationServerPlatform::SetPortMap(PortMap &&port_map) {
m_port_map = port_map;
}
const FileSpec &GDBRemoteCommunicationServerPlatform::GetDomainSocketDir() {
static FileSpec g_domainsocket_dir;
static llvm::once_flag g_once_flag;
llvm::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);
else
g_domainsocket_dir = HostInfo::GetProcessTempDir();
});
return g_domainsocket_dir;
}
FileSpec
GDBRemoteCommunicationServerPlatform::GetDomainSocketPath(const char *prefix) {
llvm::SmallString<128> socket_path;
llvm::SmallString<128> 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.GetPath().c_str(),
socket_path);
return FileSpec(socket_path.c_str());
}
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;
}