1 //===-- PlatformAndroidRemoteGDBServer.cpp --------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Host/ConnectionFileDescriptor.h"
10 #include "lldb/Host/common/TCPSocket.h"
11 #include "lldb/Utility/LLDBLog.h"
12 #include "lldb/Utility/Log.h"
13 #include "lldb/Utility/Status.h"
14 #include "lldb/Utility/UriParser.h"
15 
16 #include "PlatformAndroidRemoteGDBServer.h"
17 
18 #include <sstream>
19 
20 using namespace lldb;
21 using namespace lldb_private;
22 using namespace platform_android;
23 
24 static const lldb::pid_t g_remote_platform_pid =
25     0; // Alias for the process id of lldb-platform
26 
27 static Status ForwardPortWithAdb(
28     const uint16_t local_port, const uint16_t remote_port,
29     llvm::StringRef remote_socket_name,
30     const llvm::Optional<AdbClient::UnixSocketNamespace> &socket_namespace,
31     std::string &device_id) {
32   Log *log = GetLog(LLDBLog::Platform);
33 
34   AdbClient adb;
35   auto error = AdbClient::CreateByDeviceID(device_id, adb);
36   if (error.Fail())
37     return error;
38 
39   device_id = adb.GetDeviceID();
40   LLDB_LOGF(log, "Connected to Android device \"%s\"", device_id.c_str());
41 
42   if (remote_port != 0) {
43     LLDB_LOGF(log, "Forwarding remote TCP port %d to local TCP port %d",
44               remote_port, local_port);
45     return adb.SetPortForwarding(local_port, remote_port);
46   }
47 
48   LLDB_LOGF(log, "Forwarding remote socket \"%s\" to local TCP port %d",
49             remote_socket_name.str().c_str(), local_port);
50 
51   if (!socket_namespace)
52     return Status("Invalid socket namespace");
53 
54   return adb.SetPortForwarding(local_port, remote_socket_name,
55                                *socket_namespace);
56 }
57 
58 static Status DeleteForwardPortWithAdb(uint16_t local_port,
59                                        const std::string &device_id) {
60   AdbClient adb(device_id);
61   return adb.DeletePortForwarding(local_port);
62 }
63 
64 static Status FindUnusedPort(uint16_t &port) {
65   Status error;
66   std::unique_ptr<TCPSocket> tcp_socket(new TCPSocket(true, false));
67   if (error.Fail())
68     return error;
69 
70   error = tcp_socket->Listen("127.0.0.1:0", 1);
71   if (error.Success())
72     port = tcp_socket->GetLocalPortNumber();
73 
74   return error;
75 }
76 
77 PlatformAndroidRemoteGDBServer::PlatformAndroidRemoteGDBServer() = default;
78 
79 PlatformAndroidRemoteGDBServer::~PlatformAndroidRemoteGDBServer() {
80   for (const auto &it : m_port_forwards)
81     DeleteForwardPortWithAdb(it.second, m_device_id);
82 }
83 
84 bool PlatformAndroidRemoteGDBServer::LaunchGDBServer(lldb::pid_t &pid,
85                                                      std::string &connect_url) {
86   assert(IsConnected());
87   uint16_t remote_port = 0;
88   std::string socket_name;
89   if (!m_gdb_client_up->LaunchGDBServer("127.0.0.1", pid, remote_port,
90                                         socket_name))
91     return false;
92 
93   Log *log = GetLog(LLDBLog::Platform);
94 
95   auto error =
96       MakeConnectURL(pid, remote_port, socket_name.c_str(), connect_url);
97   if (error.Success() && log)
98     LLDB_LOGF(log, "gdbserver connect URL: %s", connect_url.c_str());
99 
100   return error.Success();
101 }
102 
103 bool PlatformAndroidRemoteGDBServer::KillSpawnedProcess(lldb::pid_t pid) {
104   assert(IsConnected());
105   DeleteForwardPort(pid);
106   return m_gdb_client_up->KillSpawnedProcess(pid);
107 }
108 
109 Status PlatformAndroidRemoteGDBServer::ConnectRemote(Args &args) {
110   m_device_id.clear();
111 
112   if (args.GetArgumentCount() != 1)
113     return Status(
114         "\"platform connect\" takes a single argument: <connect-url>");
115 
116   const char *url = args.GetArgumentAtIndex(0);
117   if (!url)
118     return Status("URL is null.");
119   llvm::Optional<URI> parsed_url = URI::Parse(url);
120   if (!parsed_url)
121     return Status("Invalid URL: %s", url);
122   if (parsed_url->hostname != "localhost")
123     m_device_id = parsed_url->hostname.str();
124 
125   m_socket_namespace.reset();
126   if (parsed_url->scheme == "unix-connect")
127     m_socket_namespace = AdbClient::UnixSocketNamespaceFileSystem;
128   else if (parsed_url->scheme == "unix-abstract-connect")
129     m_socket_namespace = AdbClient::UnixSocketNamespaceAbstract;
130 
131   std::string connect_url;
132   auto error =
133       MakeConnectURL(g_remote_platform_pid, parsed_url->port.getValueOr(0),
134                      parsed_url->path, connect_url);
135 
136   if (error.Fail())
137     return error;
138 
139   args.ReplaceArgumentAtIndex(0, connect_url);
140 
141   Log *log = GetLog(LLDBLog::Platform);
142   LLDB_LOGF(log, "Rewritten platform connect URL: %s", connect_url.c_str());
143 
144   error = PlatformRemoteGDBServer::ConnectRemote(args);
145   if (error.Fail())
146     DeleteForwardPort(g_remote_platform_pid);
147 
148   return error;
149 }
150 
151 Status PlatformAndroidRemoteGDBServer::DisconnectRemote() {
152   DeleteForwardPort(g_remote_platform_pid);
153   return PlatformRemoteGDBServer::DisconnectRemote();
154 }
155 
156 void PlatformAndroidRemoteGDBServer::DeleteForwardPort(lldb::pid_t pid) {
157   Log *log = GetLog(LLDBLog::Platform);
158 
159   auto it = m_port_forwards.find(pid);
160   if (it == m_port_forwards.end())
161     return;
162 
163   const auto port = it->second;
164   const auto error = DeleteForwardPortWithAdb(port, m_device_id);
165   if (error.Fail()) {
166     LLDB_LOGF(log,
167               "Failed to delete port forwarding (pid=%" PRIu64
168               ", port=%d, device=%s): %s",
169               pid, port, m_device_id.c_str(), error.AsCString());
170   }
171   m_port_forwards.erase(it);
172 }
173 
174 Status PlatformAndroidRemoteGDBServer::MakeConnectURL(
175     const lldb::pid_t pid, const uint16_t remote_port,
176     llvm::StringRef remote_socket_name, std::string &connect_url) {
177   static const int kAttempsNum = 5;
178 
179   Status error;
180   // There is a race possibility that somebody will occupy a port while we're
181   // in between FindUnusedPort and ForwardPortWithAdb - adding the loop to
182   // mitigate such problem.
183   for (auto i = 0; i < kAttempsNum; ++i) {
184     uint16_t local_port = 0;
185     error = FindUnusedPort(local_port);
186     if (error.Fail())
187       return error;
188 
189     error = ForwardPortWithAdb(local_port, remote_port, remote_socket_name,
190                                m_socket_namespace, m_device_id);
191     if (error.Success()) {
192       m_port_forwards[pid] = local_port;
193       std::ostringstream url_str;
194       url_str << "connect://127.0.0.1:" << local_port;
195       connect_url = url_str.str();
196       break;
197     }
198   }
199 
200   return error;
201 }
202 
203 lldb::ProcessSP PlatformAndroidRemoteGDBServer::ConnectProcess(
204     llvm::StringRef connect_url, llvm::StringRef plugin_name,
205     lldb_private::Debugger &debugger, lldb_private::Target *target,
206     lldb_private::Status &error) {
207   // We don't have the pid of the remote gdbserver when it isn't started by us
208   // but we still want to store the list of port forwards we set up in our port
209   // forward map. Generate a fake pid for these cases what won't collide with
210   // any other valid pid on android.
211   static lldb::pid_t s_remote_gdbserver_fake_pid = 0xffffffffffffffffULL;
212 
213   llvm::Optional<URI> parsed_url = URI::Parse(connect_url);
214   if (!parsed_url) {
215     error.SetErrorStringWithFormat("Invalid URL: %s",
216                                    connect_url.str().c_str());
217     return nullptr;
218   }
219 
220   std::string new_connect_url;
221   error = MakeConnectURL(s_remote_gdbserver_fake_pid--,
222                          parsed_url->port.getValueOr(0), parsed_url->path,
223                          new_connect_url);
224   if (error.Fail())
225     return nullptr;
226 
227   return PlatformRemoteGDBServer::ConnectProcess(new_connect_url, plugin_name,
228                                                  debugger, target, error);
229 }
230