/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/tools/lldb-server/lldb-platform.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- lldb-platform.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 <cerrno> |
10 | | #if defined(__APPLE__) |
11 | | #include <netinet/in.h> |
12 | | #endif |
13 | | #include <csignal> |
14 | | #include <cstdint> |
15 | | #include <cstdio> |
16 | | #include <cstdlib> |
17 | | #include <cstring> |
18 | | #if !defined(_WIN32) |
19 | | #include <sys/wait.h> |
20 | | #endif |
21 | | #include <fstream> |
22 | | #include <optional> |
23 | | |
24 | | #include "llvm/Support/FileSystem.h" |
25 | | #include "llvm/Support/WithColor.h" |
26 | | #include "llvm/Support/raw_ostream.h" |
27 | | |
28 | | #include "Acceptor.h" |
29 | | #include "LLDBServerUtilities.h" |
30 | | #include "Plugins/Process/gdb-remote/GDBRemoteCommunicationServerPlatform.h" |
31 | | #include "Plugins/Process/gdb-remote/ProcessGDBRemoteLog.h" |
32 | | #include "lldb/Host/ConnectionFileDescriptor.h" |
33 | | #include "lldb/Host/HostGetOpt.h" |
34 | | #include "lldb/Host/OptionParser.h" |
35 | | #include "lldb/Host/common/TCPSocket.h" |
36 | | #include "lldb/Utility/FileSpec.h" |
37 | | #include "lldb/Utility/Status.h" |
38 | | |
39 | | using namespace lldb; |
40 | | using namespace lldb_private; |
41 | | using namespace lldb_private::lldb_server; |
42 | | using namespace lldb_private::process_gdb_remote; |
43 | | using namespace llvm; |
44 | | |
45 | | // option descriptors for getopt_long_only() |
46 | | |
47 | | static int g_debug = 0; |
48 | | static int g_verbose = 0; |
49 | | static int g_server = 0; |
50 | | |
51 | | static struct option g_long_options[] = { |
52 | | {"debug", no_argument, &g_debug, 1}, |
53 | | {"verbose", no_argument, &g_verbose, 1}, |
54 | | {"log-file", required_argument, nullptr, 'l'}, |
55 | | {"log-channels", required_argument, nullptr, 'c'}, |
56 | | {"listen", required_argument, nullptr, 'L'}, |
57 | | {"port-offset", required_argument, nullptr, 'p'}, |
58 | | {"gdbserver-port", required_argument, nullptr, 'P'}, |
59 | | {"min-gdbserver-port", required_argument, nullptr, 'm'}, |
60 | | {"max-gdbserver-port", required_argument, nullptr, 'M'}, |
61 | | {"socket-file", required_argument, nullptr, 'f'}, |
62 | | {"server", no_argument, &g_server, 1}, |
63 | | {nullptr, 0, nullptr, 0}}; |
64 | | |
65 | | #if defined(__APPLE__) |
66 | 4 | #define LOW_PORT (IPPORT_RESERVED) |
67 | 2 | #define HIGH_PORT (IPPORT_HIFIRSTAUTO) |
68 | | #else |
69 | | #define LOW_PORT (1024u) |
70 | | #define HIGH_PORT (49151u) |
71 | | #endif |
72 | | |
73 | | #if !defined(_WIN32) |
74 | | // Watch for signals |
75 | 0 | static void signal_handler(int signo) { |
76 | 0 | switch (signo) { |
77 | 0 | case SIGHUP: |
78 | | // Use SIGINT first, if that does not work, use SIGHUP as a last resort. |
79 | | // And we should not call exit() here because it results in the global |
80 | | // destructors to be invoked and wreaking havoc on the threads still |
81 | | // running. |
82 | 0 | llvm::errs() << "SIGHUP received, exiting lldb-server...\n"; |
83 | 0 | abort(); |
84 | 0 | break; |
85 | 0 | } |
86 | 0 | } |
87 | | #endif |
88 | | |
89 | 1 | static void display_usage(const char *progname, const char *subcommand) { |
90 | 1 | fprintf(stderr, "Usage:\n %s %s [--log-file log-file-name] [--log-channels " |
91 | 1 | "log-channel-list] [--port-file port-file-path] --server " |
92 | 1 | "--listen port\n", |
93 | 1 | progname, subcommand); |
94 | 1 | exit(0); |
95 | 1 | } |
96 | | |
97 | | static Status save_socket_id_to_file(const std::string &socket_id, |
98 | 0 | const FileSpec &file_spec) { |
99 | 0 | FileSpec temp_file_spec(file_spec.GetDirectory().GetStringRef()); |
100 | 0 | Status error(llvm::sys::fs::create_directory(temp_file_spec.GetPath())); |
101 | 0 | if (error.Fail()) |
102 | 0 | return Status("Failed to create directory %s: %s", |
103 | 0 | temp_file_spec.GetPath().c_str(), error.AsCString()); |
104 | | |
105 | 0 | Status status; |
106 | 0 | if (auto Err = llvm::writeToOutput(file_spec.GetPath(), |
107 | 0 | [&socket_id](llvm::raw_ostream &OS) { |
108 | 0 | OS << socket_id; |
109 | 0 | return llvm::Error::success(); |
110 | 0 | })) |
111 | 0 | return Status("Failed to atomically write file %s: %s", |
112 | 0 | file_spec.GetPath().c_str(), |
113 | 0 | llvm::toString(std::move(Err)).c_str()); |
114 | 0 | return status; |
115 | 0 | } |
116 | | |
117 | | // main |
118 | 1 | int main_platform(int argc, char *argv[]) { |
119 | 1 | const char *progname = argv[0]; |
120 | 1 | const char *subcommand = argv[1]; |
121 | 1 | argc--; |
122 | 1 | argv++; |
123 | 1 | #if !defined(_WIN32) |
124 | 1 | signal(SIGPIPE, SIG_IGN); |
125 | 1 | signal(SIGHUP, signal_handler); |
126 | 1 | #endif |
127 | 1 | int long_option_index = 0; |
128 | 1 | Status error; |
129 | 1 | std::string listen_host_port; |
130 | 1 | int ch; |
131 | | |
132 | 1 | std::string log_file; |
133 | 1 | StringRef |
134 | 1 | log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" |
135 | | |
136 | 1 | GDBRemoteCommunicationServerPlatform::PortMap gdbserver_portmap; |
137 | 1 | int min_gdbserver_port = 0; |
138 | 1 | int max_gdbserver_port = 0; |
139 | 1 | uint16_t port_offset = 0; |
140 | | |
141 | 1 | FileSpec socket_file; |
142 | 1 | bool show_usage = false; |
143 | 1 | int option_error = 0; |
144 | 1 | int socket_error = -1; |
145 | | |
146 | 1 | std::string short_options(OptionParser::GetShortOptionString(g_long_options)); |
147 | | |
148 | | #if __GLIBC__ |
149 | | optind = 0; |
150 | | #else |
151 | 1 | optreset = 1; |
152 | 1 | optind = 1; |
153 | 1 | #endif |
154 | | |
155 | 5 | while ((ch = getopt_long_only(argc, argv, short_options.c_str(), |
156 | 5 | g_long_options, &long_option_index)) != -1) { |
157 | 4 | switch (ch) { |
158 | 1 | case 0: // Any optional that auto set themselves will return 0 |
159 | 1 | break; |
160 | | |
161 | 1 | case 'L': |
162 | 1 | listen_host_port.append(optarg); |
163 | 1 | break; |
164 | | |
165 | 0 | case 'l': // Set Log File |
166 | 0 | if (optarg && optarg[0]) |
167 | 0 | log_file.assign(optarg); |
168 | 0 | break; |
169 | | |
170 | 0 | case 'c': // Log Channels |
171 | 0 | if (optarg && optarg[0]) |
172 | 0 | log_channels = StringRef(optarg); |
173 | 0 | break; |
174 | | |
175 | 0 | case 'f': // Socket file |
176 | 0 | if (optarg && optarg[0]) |
177 | 0 | socket_file.SetFile(optarg, FileSpec::Style::native); |
178 | 0 | break; |
179 | | |
180 | 0 | case 'p': { |
181 | 0 | if (!llvm::to_integer(optarg, port_offset)) { |
182 | 0 | WithColor::error() << "invalid port offset string " << optarg << "\n"; |
183 | 0 | option_error = 4; |
184 | 0 | break; |
185 | 0 | } |
186 | 0 | if (port_offset < LOW_PORT || port_offset > HIGH_PORT) { |
187 | 0 | WithColor::error() << llvm::formatv( |
188 | 0 | "port offset {0} is not in the " |
189 | 0 | "valid user port range of {1} - {2}\n", |
190 | 0 | port_offset, LOW_PORT, HIGH_PORT); |
191 | 0 | option_error = 5; |
192 | 0 | } |
193 | 0 | } break; |
194 | | |
195 | 0 | case 'P': |
196 | 1 | case 'm': |
197 | 2 | case 'M': { |
198 | 2 | uint16_t portnum; |
199 | 2 | if (!llvm::to_integer(optarg, portnum)) { |
200 | 0 | WithColor::error() << "invalid port number string " << optarg << "\n"; |
201 | 0 | option_error = 2; |
202 | 0 | break; |
203 | 0 | } |
204 | 2 | if (portnum < LOW_PORT || portnum > HIGH_PORT) { |
205 | 0 | WithColor::error() << llvm::formatv( |
206 | 0 | "port number {0} is not in the " |
207 | 0 | "valid user port range of {1} - {2}\n", |
208 | 0 | portnum, LOW_PORT, HIGH_PORT); |
209 | 0 | option_error = 1; |
210 | 0 | break; |
211 | 0 | } |
212 | 2 | if (ch == 'P') |
213 | 0 | gdbserver_portmap.AllowPort(portnum); |
214 | 2 | else if (ch == 'm') |
215 | 1 | min_gdbserver_port = portnum; |
216 | 1 | else |
217 | 1 | max_gdbserver_port = portnum; |
218 | 2 | } break; |
219 | | |
220 | 0 | case 'h': /* fall-through is intentional */ |
221 | 0 | case '?': |
222 | 0 | show_usage = true; |
223 | 0 | break; |
224 | 4 | } |
225 | 4 | } |
226 | | |
227 | 1 | if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0)) |
228 | 0 | return -1; |
229 | | |
230 | | // Make a port map for a port range that was specified. |
231 | 1 | if (min_gdbserver_port && min_gdbserver_port < max_gdbserver_port) { |
232 | 0 | gdbserver_portmap = GDBRemoteCommunicationServerPlatform::PortMap( |
233 | 0 | min_gdbserver_port, max_gdbserver_port); |
234 | 1 | } else if (min_gdbserver_port || max_gdbserver_port0 ) { |
235 | 1 | WithColor::error() << llvm::formatv( |
236 | 1 | "--min-gdbserver-port ({0}) is not lower than " |
237 | 1 | "--max-gdbserver-port ({1})\n", |
238 | 1 | min_gdbserver_port, max_gdbserver_port); |
239 | 1 | option_error = 3; |
240 | 1 | } |
241 | | |
242 | | // Print usage and exit if no listening port is specified. |
243 | 1 | if (listen_host_port.empty()) |
244 | 0 | show_usage = true; |
245 | | |
246 | 1 | if (show_usage || option_error) { |
247 | 1 | display_usage(progname, subcommand); |
248 | 1 | exit(option_error); |
249 | 1 | } |
250 | | |
251 | | // Skip any options we consumed with getopt_long_only. |
252 | 0 | argc -= optind; |
253 | 0 | argv += optind; |
254 | 0 | lldb_private::Args inferior_arguments; |
255 | 0 | inferior_arguments.SetArguments(argc, const_cast<const char **>(argv)); |
256 | |
|
257 | 0 | const bool children_inherit_listen_socket = false; |
258 | | // the test suite makes many connections in parallel, let's not miss any. |
259 | | // The highest this should get reasonably is a function of the number |
260 | | // of target CPUs. For now, let's just use 100. |
261 | 0 | const int backlog = 100; |
262 | |
|
263 | 0 | std::unique_ptr<Acceptor> acceptor_up(Acceptor::Create( |
264 | 0 | listen_host_port, children_inherit_listen_socket, error)); |
265 | 0 | if (error.Fail()) { |
266 | 0 | fprintf(stderr, "failed to create acceptor: %s", error.AsCString()); |
267 | 0 | exit(socket_error); |
268 | 0 | } |
269 | | |
270 | 0 | error = acceptor_up->Listen(backlog); |
271 | 0 | if (error.Fail()) { |
272 | 0 | printf("failed to listen: %s\n", error.AsCString()); |
273 | 0 | exit(socket_error); |
274 | 0 | } |
275 | 0 | if (socket_file) { |
276 | 0 | error = |
277 | 0 | save_socket_id_to_file(acceptor_up->GetLocalSocketId(), socket_file); |
278 | 0 | if (error.Fail()) { |
279 | 0 | fprintf(stderr, "failed to write socket id to %s: %s\n", |
280 | 0 | socket_file.GetPath().c_str(), error.AsCString()); |
281 | 0 | return 1; |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | 0 | do { |
286 | 0 | GDBRemoteCommunicationServerPlatform platform( |
287 | 0 | acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme()); |
288 | |
|
289 | 0 | if (port_offset > 0) |
290 | 0 | platform.SetPortOffset(port_offset); |
291 | |
|
292 | 0 | if (!gdbserver_portmap.empty()) { |
293 | 0 | platform.SetPortMap(std::move(gdbserver_portmap)); |
294 | 0 | } |
295 | |
|
296 | 0 | const bool children_inherit_accept_socket = true; |
297 | 0 | Connection *conn = nullptr; |
298 | 0 | error = acceptor_up->Accept(children_inherit_accept_socket, conn); |
299 | 0 | if (error.Fail()) { |
300 | 0 | WithColor::error() << error.AsCString() << '\n'; |
301 | 0 | exit(socket_error); |
302 | 0 | } |
303 | 0 | printf("Connection established.\n"); |
304 | 0 | if (g_server) { |
305 | | // Collect child zombie processes. |
306 | 0 | #if !defined(_WIN32) |
307 | 0 | while (waitpid(-1, nullptr, WNOHANG) > 0) |
308 | 0 | ; |
309 | 0 | #endif |
310 | 0 | if (fork()) { |
311 | | // Parent doesn't need a connection to the lldb client |
312 | 0 | delete conn; |
313 | | |
314 | | // Parent will continue to listen for new connections. |
315 | 0 | continue; |
316 | 0 | } else { |
317 | | // Child process will handle the connection and exit. |
318 | 0 | g_server = 0; |
319 | | // Listening socket is owned by parent process. |
320 | 0 | acceptor_up.release(); |
321 | 0 | } |
322 | 0 | } else { |
323 | | // If not running as a server, this process will not accept |
324 | | // connections while a connection is active. |
325 | 0 | acceptor_up.reset(); |
326 | 0 | } |
327 | 0 | platform.SetConnection(std::unique_ptr<Connection>(conn)); |
328 | |
|
329 | 0 | if (platform.IsConnected()) { |
330 | 0 | if (inferior_arguments.GetArgumentCount() > 0) { |
331 | 0 | lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; |
332 | 0 | std::optional<uint16_t> port = 0; |
333 | 0 | std::string socket_name; |
334 | 0 | Status error = platform.LaunchGDBServer(inferior_arguments, |
335 | 0 | "", // hostname |
336 | 0 | pid, port, socket_name); |
337 | 0 | if (error.Success()) |
338 | 0 | platform.SetPendingGdbServer(pid, *port, socket_name); |
339 | 0 | else |
340 | 0 | fprintf(stderr, "failed to start gdbserver: %s\n", error.AsCString()); |
341 | 0 | } |
342 | |
|
343 | 0 | bool interrupt = false; |
344 | 0 | bool done = false; |
345 | 0 | while (!interrupt && !done) { |
346 | 0 | if (platform.GetPacketAndSendResponse(std::nullopt, error, interrupt, |
347 | 0 | done) != |
348 | 0 | GDBRemoteCommunication::PacketResult::Success) |
349 | 0 | break; |
350 | 0 | } |
351 | |
|
352 | 0 | if (error.Fail()) |
353 | 0 | WithColor::error() << error.AsCString() << '\n'; |
354 | 0 | } |
355 | 0 | } while (g_server); |
356 | | |
357 | 0 | fprintf(stderr, "lldb-server exiting...\n"); |
358 | |
|
359 | 0 | return 0; |
360 | 0 | } |