//===-- 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 // C Includes // C++ Includes #include #include // Other libraries and framework includes #include "lldb/Core/Log.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/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() : GDBRemoteCommunicationServerCommon ("gdb-remote.server", "gdb-remote.server.rx_packet"), 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_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); assert(ok); Error error = StartDebugserverProcess ( platform_ip.c_str(), port, debugserver_launch_info, port); 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); char response[256]; const int response_len = ::snprintf (response, sizeof(response), "pid:%" PRIu64 ";port:%u;", debugserver_pid, port + m_port_offset); assert (response_len < (int)sizeof(response)); PacketResult packet_result = SendPacketNoLock (response, response_len); 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_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(); 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; } void GDBRemoteCommunicationServerPlatform::SetPortOffset (uint16_t port_offset) { m_port_offset = port_offset; }