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