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 | {.name: "debug" , no_argument, .flag: &g_debug, .val: 1}, |
53 | {.name: "verbose" , no_argument, .flag: &g_verbose, .val: 1}, |
54 | {.name: "log-file" , required_argument, .flag: nullptr, .val: 'l'}, |
55 | {.name: "log-channels" , required_argument, .flag: nullptr, .val: 'c'}, |
56 | {.name: "listen" , required_argument, .flag: nullptr, .val: 'L'}, |
57 | {.name: "port-offset" , required_argument, .flag: nullptr, .val: 'p'}, |
58 | {.name: "gdbserver-port" , required_argument, .flag: nullptr, .val: 'P'}, |
59 | {.name: "min-gdbserver-port" , required_argument, .flag: nullptr, .val: 'm'}, |
60 | {.name: "max-gdbserver-port" , required_argument, .flag: nullptr, .val: 'M'}, |
61 | {.name: "socket-file" , required_argument, .flag: nullptr, .val: 'f'}, |
62 | {.name: "server" , no_argument, .flag: &g_server, .val: 1}, |
63 | {.name: nullptr, .has_arg: 0, .flag: nullptr, .val: 0}}; |
64 | |
65 | #if defined(__APPLE__) |
66 | #define LOW_PORT (IPPORT_RESERVED) |
67 | #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 | static void signal_handler(int signo) { |
76 | switch (signo) { |
77 | 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 | llvm::errs() << "SIGHUP received, exiting lldb-server...\n" ; |
83 | abort(); |
84 | break; |
85 | } |
86 | } |
87 | #endif |
88 | |
89 | static void display_usage(const char *progname, const char *subcommand) { |
90 | fprintf(stderr, format: "Usage:\n %s %s [--log-file log-file-name] [--log-channels " |
91 | "log-channel-list] [--port-file port-file-path] --server " |
92 | "--listen port\n" , |
93 | progname, subcommand); |
94 | exit(status: 0); |
95 | } |
96 | |
97 | static Status save_socket_id_to_file(const std::string &socket_id, |
98 | const FileSpec &file_spec) { |
99 | FileSpec temp_file_spec(file_spec.GetDirectory().GetStringRef()); |
100 | Status error(llvm::sys::fs::create_directory(path: temp_file_spec.GetPath())); |
101 | if (error.Fail()) |
102 | return Status("Failed to create directory %s: %s" , |
103 | temp_file_spec.GetPath().c_str(), error.AsCString()); |
104 | |
105 | Status status; |
106 | if (auto Err = llvm::writeToOutput(OutputFileName: file_spec.GetPath(), |
107 | Write: [&socket_id](llvm::raw_ostream &OS) { |
108 | OS << socket_id; |
109 | return llvm::Error::success(); |
110 | })) |
111 | return Status("Failed to atomically write file %s: %s" , |
112 | file_spec.GetPath().c_str(), |
113 | llvm::toString(E: std::move(Err)).c_str()); |
114 | return status; |
115 | } |
116 | |
117 | // main |
118 | int main_platform(int argc, char *argv[]) { |
119 | const char *progname = argv[0]; |
120 | const char *subcommand = argv[1]; |
121 | argc--; |
122 | argv++; |
123 | #if !defined(_WIN32) |
124 | signal(SIGPIPE, SIG_IGN); |
125 | signal(SIGHUP, handler: signal_handler); |
126 | #endif |
127 | int long_option_index = 0; |
128 | Status error; |
129 | std::string listen_host_port; |
130 | int ch; |
131 | |
132 | std::string log_file; |
133 | StringRef |
134 | log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" |
135 | |
136 | GDBRemoteCommunicationServerPlatform::PortMap gdbserver_portmap; |
137 | int min_gdbserver_port = 0; |
138 | int max_gdbserver_port = 0; |
139 | uint16_t port_offset = 0; |
140 | |
141 | FileSpec socket_file; |
142 | bool show_usage = false; |
143 | int option_error = 0; |
144 | int socket_error = -1; |
145 | |
146 | std::string short_options(OptionParser::GetShortOptionString(long_options: g_long_options)); |
147 | |
148 | #if __GLIBC__ |
149 | optind = 0; |
150 | #else |
151 | optreset = 1; |
152 | optind = 1; |
153 | #endif |
154 | |
155 | while ((ch = getopt_long_only(argc: argc, argv: argv, shortopts: short_options.c_str(), |
156 | longopts: g_long_options, longind: &long_option_index)) != -1) { |
157 | switch (ch) { |
158 | case 0: // Any optional that auto set themselves will return 0 |
159 | break; |
160 | |
161 | case 'L': |
162 | listen_host_port.append(s: optarg); |
163 | break; |
164 | |
165 | case 'l': // Set Log File |
166 | if (optarg && optarg[0]) |
167 | log_file.assign(s: optarg); |
168 | break; |
169 | |
170 | case 'c': // Log Channels |
171 | if (optarg && optarg[0]) |
172 | log_channels = StringRef(optarg); |
173 | break; |
174 | |
175 | case 'f': // Socket file |
176 | if (optarg && optarg[0]) |
177 | socket_file.SetFile(path: optarg, style: FileSpec::Style::native); |
178 | break; |
179 | |
180 | case 'p': { |
181 | if (!llvm::to_integer(S: optarg, Num&: port_offset)) { |
182 | WithColor::error() << "invalid port offset string " << optarg << "\n" ; |
183 | option_error = 4; |
184 | break; |
185 | } |
186 | if (port_offset < LOW_PORT || port_offset > HIGH_PORT) { |
187 | WithColor::error() << llvm::formatv( |
188 | Fmt: "port offset {0} is not in the " |
189 | "valid user port range of {1} - {2}\n" , |
190 | Vals&: port_offset, LOW_PORT, HIGH_PORT); |
191 | option_error = 5; |
192 | } |
193 | } break; |
194 | |
195 | case 'P': |
196 | case 'm': |
197 | case 'M': { |
198 | uint16_t portnum; |
199 | if (!llvm::to_integer(S: optarg, Num&: portnum)) { |
200 | WithColor::error() << "invalid port number string " << optarg << "\n" ; |
201 | option_error = 2; |
202 | break; |
203 | } |
204 | if (portnum < LOW_PORT || portnum > HIGH_PORT) { |
205 | WithColor::error() << llvm::formatv( |
206 | Fmt: "port number {0} is not in the " |
207 | "valid user port range of {1} - {2}\n" , |
208 | Vals&: portnum, LOW_PORT, HIGH_PORT); |
209 | option_error = 1; |
210 | break; |
211 | } |
212 | if (ch == 'P') |
213 | gdbserver_portmap.AllowPort(port: portnum); |
214 | else if (ch == 'm') |
215 | min_gdbserver_port = portnum; |
216 | else |
217 | max_gdbserver_port = portnum; |
218 | } break; |
219 | |
220 | case 'h': /* fall-through is intentional */ |
221 | case '?': |
222 | show_usage = true; |
223 | break; |
224 | } |
225 | } |
226 | |
227 | if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, log_options: 0)) |
228 | return -1; |
229 | |
230 | // Make a port map for a port range that was specified. |
231 | if (min_gdbserver_port && min_gdbserver_port < max_gdbserver_port) { |
232 | gdbserver_portmap = GDBRemoteCommunicationServerPlatform::PortMap( |
233 | min_gdbserver_port, max_gdbserver_port); |
234 | } else if (min_gdbserver_port || max_gdbserver_port) { |
235 | WithColor::error() << llvm::formatv( |
236 | Fmt: "--min-gdbserver-port ({0}) is not lower than " |
237 | "--max-gdbserver-port ({1})\n" , |
238 | Vals&: min_gdbserver_port, Vals&: max_gdbserver_port); |
239 | option_error = 3; |
240 | } |
241 | |
242 | // Print usage and exit if no listening port is specified. |
243 | if (listen_host_port.empty()) |
244 | show_usage = true; |
245 | |
246 | if (show_usage || option_error) { |
247 | display_usage(progname, subcommand); |
248 | exit(status: option_error); |
249 | } |
250 | |
251 | // Skip any options we consumed with getopt_long_only. |
252 | argc -= optind; |
253 | argv += optind; |
254 | lldb_private::Args inferior_arguments; |
255 | inferior_arguments.SetArguments(argc, argv: const_cast<const char **>(argv)); |
256 | |
257 | 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 | const int backlog = 100; |
262 | |
263 | std::unique_ptr<Acceptor> acceptor_up(Acceptor::Create( |
264 | name: listen_host_port, child_processes_inherit: children_inherit_listen_socket, error)); |
265 | if (error.Fail()) { |
266 | fprintf(stderr, format: "failed to create acceptor: %s" , error.AsCString()); |
267 | exit(status: socket_error); |
268 | } |
269 | |
270 | error = acceptor_up->Listen(backlog); |
271 | if (error.Fail()) { |
272 | printf(format: "failed to listen: %s\n" , error.AsCString()); |
273 | exit(status: socket_error); |
274 | } |
275 | if (socket_file) { |
276 | error = |
277 | save_socket_id_to_file(socket_id: acceptor_up->GetLocalSocketId(), file_spec: socket_file); |
278 | if (error.Fail()) { |
279 | fprintf(stderr, format: "failed to write socket id to %s: %s\n" , |
280 | socket_file.GetPath().c_str(), error.AsCString()); |
281 | return 1; |
282 | } |
283 | } |
284 | |
285 | do { |
286 | GDBRemoteCommunicationServerPlatform platform( |
287 | acceptor_up->GetSocketProtocol(), acceptor_up->GetSocketScheme()); |
288 | |
289 | if (port_offset > 0) |
290 | platform.SetPortOffset(port_offset); |
291 | |
292 | if (!gdbserver_portmap.empty()) { |
293 | platform.SetPortMap(std::move(gdbserver_portmap)); |
294 | } |
295 | |
296 | const bool children_inherit_accept_socket = true; |
297 | Connection *conn = nullptr; |
298 | error = acceptor_up->Accept(child_processes_inherit: children_inherit_accept_socket, conn); |
299 | if (error.Fail()) { |
300 | WithColor::error() << error.AsCString() << '\n'; |
301 | exit(status: socket_error); |
302 | } |
303 | printf(format: "Connection established.\n" ); |
304 | if (g_server) { |
305 | // Collect child zombie processes. |
306 | #if !defined(_WIN32) |
307 | while (waitpid(pid: -1, stat_loc: nullptr, WNOHANG) > 0) |
308 | ; |
309 | #endif |
310 | if (fork()) { |
311 | // Parent doesn't need a connection to the lldb client |
312 | delete conn; |
313 | |
314 | // Parent will continue to listen for new connections. |
315 | continue; |
316 | } else { |
317 | // Child process will handle the connection and exit. |
318 | g_server = 0; |
319 | // Listening socket is owned by parent process. |
320 | acceptor_up.release(); |
321 | } |
322 | } else { |
323 | // If not running as a server, this process will not accept |
324 | // connections while a connection is active. |
325 | acceptor_up.reset(); |
326 | } |
327 | platform.SetConnection(std::unique_ptr<Connection>(conn)); |
328 | |
329 | if (platform.IsConnected()) { |
330 | if (inferior_arguments.GetArgumentCount() > 0) { |
331 | lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; |
332 | std::optional<uint16_t> port = 0; |
333 | std::string socket_name; |
334 | Status error = platform.LaunchGDBServer(args: inferior_arguments, |
335 | hostname: "" , // hostname |
336 | pid, port, socket_name); |
337 | if (error.Success()) |
338 | platform.SetPendingGdbServer(pid, port: *port, socket_name); |
339 | else |
340 | fprintf(stderr, format: "failed to start gdbserver: %s\n" , error.AsCString()); |
341 | } |
342 | |
343 | bool interrupt = false; |
344 | bool done = false; |
345 | while (!interrupt && !done) { |
346 | if (platform.GetPacketAndSendResponse(timeout: std::nullopt, error, interrupt, |
347 | quit&: done) != |
348 | GDBRemoteCommunication::PacketResult::Success) |
349 | break; |
350 | } |
351 | |
352 | if (error.Fail()) |
353 | WithColor::error() << error.AsCString() << '\n'; |
354 | } |
355 | } while (g_server); |
356 | |
357 | fprintf(stderr, format: "lldb-server exiting...\n" ); |
358 | |
359 | return 0; |
360 | } |
361 | |