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