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