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