1 | //===-- lldb-dap.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 "DAP.h" |
10 | #include "DAPLog.h" |
11 | #include "EventHelper.h" |
12 | #include "Handler/RequestHandler.h" |
13 | #include "RunInTerminal.h" |
14 | #include "Transport.h" |
15 | #include "lldb/API/SBDebugger.h" |
16 | #include "lldb/API/SBStream.h" |
17 | #include "lldb/Host/Config.h" |
18 | #include "lldb/Host/File.h" |
19 | #include "lldb/Host/MainLoop.h" |
20 | #include "lldb/Host/MainLoopBase.h" |
21 | #include "lldb/Host/MemoryMonitor.h" |
22 | #include "lldb/Host/Socket.h" |
23 | #include "lldb/Utility/Status.h" |
24 | #include "lldb/Utility/UriParser.h" |
25 | #include "lldb/lldb-forward.h" |
26 | #include "llvm/ADT/ArrayRef.h" |
27 | #include "llvm/ADT/ScopeExit.h" |
28 | #include "llvm/ADT/StringExtras.h" |
29 | #include "llvm/ADT/StringRef.h" |
30 | #include "llvm/Option/Arg.h" |
31 | #include "llvm/Option/ArgList.h" |
32 | #include "llvm/Option/OptTable.h" |
33 | #include "llvm/Option/Option.h" |
34 | #include "llvm/Support/CommandLine.h" |
35 | #include "llvm/Support/Error.h" |
36 | #include "llvm/Support/FileSystem.h" |
37 | #include "llvm/Support/InitLLVM.h" |
38 | #include "llvm/Support/Path.h" |
39 | #include "llvm/Support/PrettyStackTrace.h" |
40 | #include "llvm/Support/Signals.h" |
41 | #include "llvm/Support/Threading.h" |
42 | #include "llvm/Support/raw_ostream.h" |
43 | #include <condition_variable> |
44 | #include <cstdio> |
45 | #include <cstdlib> |
46 | #include <fcntl.h> |
47 | #include <map> |
48 | #include <memory> |
49 | #include <mutex> |
50 | #include <string> |
51 | #include <system_error> |
52 | #include <thread> |
53 | #include <utility> |
54 | #include <vector> |
55 | |
56 | #if defined(_WIN32) |
57 | // We need to #define NOMINMAX in order to skip `min()` and `max()` macro |
58 | // definitions that conflict with other system headers. |
59 | // We also need to #undef GetObject (which is defined to GetObjectW) because |
60 | // the JSON code we use also has methods named `GetObject()` and we conflict |
61 | // against these. |
62 | #define NOMINMAX |
63 | #include <windows.h> |
64 | #undef GetObject |
65 | #include <io.h> |
66 | typedef int socklen_t; |
67 | #else |
68 | #include <netinet/in.h> |
69 | #include <sys/socket.h> |
70 | #include <sys/un.h> |
71 | #include <unistd.h> |
72 | #endif |
73 | |
74 | #if defined(__linux__) |
75 | #include <sys/prctl.h> |
76 | #endif |
77 | |
78 | using namespace lldb_dap; |
79 | using lldb_private::File; |
80 | using lldb_private::IOObject; |
81 | using lldb_private::MainLoop; |
82 | using lldb_private::MainLoopBase; |
83 | using lldb_private::NativeFile; |
84 | using lldb_private::Socket; |
85 | using lldb_private::Status; |
86 | |
87 | namespace { |
88 | using namespace llvm::opt; |
89 | |
90 | enum ID { |
91 | OPT_INVALID = 0, // This is not an option ID. |
92 | #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), |
93 | #include "Options.inc" |
94 | #undef OPTION |
95 | }; |
96 | |
97 | #define OPTTABLE_STR_TABLE_CODE |
98 | #include "Options.inc" |
99 | #undef OPTTABLE_STR_TABLE_CODE |
100 | |
101 | #define OPTTABLE_PREFIXES_TABLE_CODE |
102 | #include "Options.inc" |
103 | #undef OPTTABLE_PREFIXES_TABLE_CODE |
104 | |
105 | static constexpr llvm::opt::OptTable::Info InfoTable[] = { |
106 | #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), |
107 | #include "Options.inc" |
108 | #undef OPTION |
109 | }; |
110 | class LLDBDAPOptTable : public llvm::opt::GenericOptTable { |
111 | public: |
112 | LLDBDAPOptTable() |
113 | : llvm::opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, |
114 | InfoTable, true) {} |
115 | }; |
116 | } // anonymous namespace |
117 | |
118 | static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) { |
119 | std::string usage_str = tool_name.str() + " options" ; |
120 | table.printHelp(OS&: llvm::outs(), Usage: usage_str.c_str(), Title: "LLDB DAP" , ShowHidden: false); |
121 | |
122 | llvm::outs() << R"___( |
123 | EXAMPLES: |
124 | The debug adapter can be started in two modes. |
125 | |
126 | Running lldb-dap without any arguments will start communicating with the |
127 | parent over stdio. Passing a --connection URI will cause lldb-dap to listen |
128 | for a connection in the specified mode. |
129 | |
130 | lldb-dap --connection connection://localhost:<port> |
131 | |
132 | Passing --wait-for-debugger will pause the process at startup and wait for a |
133 | debugger to attach to the process. |
134 | |
135 | lldb-dap -g |
136 | )___" ; |
137 | } |
138 | |
139 | static void PrintVersion() { |
140 | llvm::outs() << "lldb-dap: " ; |
141 | llvm::cl::PrintVersionMessage(); |
142 | llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n'; |
143 | } |
144 | |
145 | // If --launch-target is provided, this instance of lldb-dap becomes a |
146 | // runInTerminal launcher. It will ultimately launch the program specified in |
147 | // the --launch-target argument, which is the original program the user wanted |
148 | // to debug. This is done in such a way that the actual debug adapter can |
149 | // place breakpoints at the beginning of the program. |
150 | // |
151 | // The launcher will communicate with the debug adapter using a fifo file in the |
152 | // directory specified in the --comm-file argument. |
153 | // |
154 | // Regarding the actual flow, this launcher will first notify the debug adapter |
155 | // of its pid. Then, the launcher will be in a pending state waiting to be |
156 | // attached by the adapter. |
157 | // |
158 | // Once attached and resumed, the launcher will exec and become the program |
159 | // specified by --launch-target, which is the original target the |
160 | // user wanted to run. |
161 | // |
162 | // In case of errors launching the target, a suitable error message will be |
163 | // emitted to the debug adapter. |
164 | static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, |
165 | llvm::StringRef comm_file, |
166 | lldb::pid_t debugger_pid, |
167 | char *argv[]) { |
168 | #if defined(_WIN32) |
169 | return llvm::createStringError( |
170 | "runInTerminal is only supported on POSIX systems" ); |
171 | #else |
172 | |
173 | // On Linux with the Yama security module enabled, a process can only attach |
174 | // to its descendants by default. In the runInTerminal case the target |
175 | // process is launched by the client so we need to allow tracing explicitly. |
176 | #if defined(__linux__) |
177 | if (debugger_pid != LLDB_INVALID_PROCESS_ID) |
178 | (void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0); |
179 | #endif |
180 | |
181 | RunInTerminalLauncherCommChannel comm_channel(comm_file); |
182 | if (llvm::Error err = comm_channel.NotifyPid()) |
183 | return err; |
184 | |
185 | // We will wait to be attached with a timeout. We don't wait indefinitely |
186 | // using a signal to prevent being paused forever. |
187 | |
188 | // This env var should be used only for tests. |
189 | const char *timeout_env_var = getenv(name: "LLDB_DAP_RIT_TIMEOUT_IN_MS" ); |
190 | int timeout_in_ms = |
191 | timeout_env_var != nullptr ? atoi(nptr: timeout_env_var) : 20000; |
192 | if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches( |
193 | timeout: std::chrono::milliseconds(timeout_in_ms))) { |
194 | return err; |
195 | } |
196 | |
197 | const char *target = target_arg.getValue(); |
198 | execvp(file: target, argv: argv); |
199 | |
200 | std::string error = std::strerror(errno); |
201 | comm_channel.NotifyError(error); |
202 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
203 | S: std::move(error)); |
204 | #endif |
205 | } |
206 | |
207 | /// used only by TestVSCode_redirection_to_console.py |
208 | static void redirection_test() { |
209 | printf(format: "stdout message\n" ); |
210 | fprintf(stderr, format: "stderr message\n" ); |
211 | fflush(stdout); |
212 | fflush(stderr); |
213 | } |
214 | |
215 | /// Duplicates a file descriptor, setting FD_CLOEXEC if applicable. |
216 | static int DuplicateFileDescriptor(int fd) { |
217 | #if defined(F_DUPFD_CLOEXEC) |
218 | // Ensure FD_CLOEXEC is set. |
219 | return ::fcntl(fd: fd, F_DUPFD_CLOEXEC, 0); |
220 | #else |
221 | return ::dup(fd); |
222 | #endif |
223 | } |
224 | |
225 | static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>> |
226 | validateConnection(llvm::StringRef conn) { |
227 | auto uri = lldb_private::URI::Parse(uri: conn); |
228 | |
229 | if (uri && (uri->scheme == "tcp" || uri->scheme == "connect" || |
230 | !uri->hostname.empty() || uri->port)) { |
231 | return std::make_pair( |
232 | x: Socket::ProtocolTcp, |
233 | y: formatv(Fmt: "[{0}]:{1}" , Vals: uri->hostname.empty() ? "0.0.0.0" : uri->hostname, |
234 | Vals: uri->port.value_or(u: 0))); |
235 | } |
236 | |
237 | if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" || |
238 | uri->path != "/" )) { |
239 | return std::make_pair(x: Socket::ProtocolUnixDomain, y: uri->path.str()); |
240 | } |
241 | |
242 | return llvm::createStringError( |
243 | Fmt: "Unsupported connection specifier, expected 'unix-connect:///path' or " |
244 | "'connect://[host]:port', got '%s'." , |
245 | Vals: conn.str().c_str()); |
246 | } |
247 | |
248 | static llvm::Error |
249 | serveConnection(const Socket::SocketProtocol &protocol, const std::string &name, |
250 | Log *log, const ReplMode default_repl_mode, |
251 | const std::vector<std::string> &pre_init_commands) { |
252 | Status status; |
253 | static std::unique_ptr<Socket> listener = Socket::Create(protocol, error&: status); |
254 | if (status.Fail()) { |
255 | return status.takeError(); |
256 | } |
257 | |
258 | status = listener->Listen(name, /*backlog=*/5); |
259 | if (status.Fail()) { |
260 | return status.takeError(); |
261 | } |
262 | |
263 | std::string address = llvm::join(R: listener->GetListeningConnectionURI(), Separator: ", " ); |
264 | DAP_LOG(log, "started with connection listeners {0}" , address); |
265 | |
266 | llvm::outs() << "Listening for: " << address << "\n" ; |
267 | // Ensure listening address are flushed for calles to retrieve the resolve |
268 | // address. |
269 | llvm::outs().flush(); |
270 | |
271 | static MainLoop g_loop; |
272 | llvm::sys::SetInterruptFunction([]() { |
273 | g_loop.AddPendingCallback( |
274 | callback: [](MainLoopBase &loop) { loop.RequestTermination(); }); |
275 | }); |
276 | std::condition_variable dap_sessions_condition; |
277 | std::mutex dap_sessions_mutex; |
278 | std::map<IOObject *, DAP *> dap_sessions; |
279 | unsigned int clientCount = 0; |
280 | auto handle = listener->Accept(loop&: g_loop, sock_cb: [=, &dap_sessions_condition, |
281 | &dap_sessions_mutex, &dap_sessions, |
282 | &clientCount]( |
283 | std::unique_ptr<Socket> sock) { |
284 | std::string client_name = llvm::formatv(Fmt: "client_{0}" , Vals: clientCount++).str(); |
285 | DAP_LOG(log, "({0}) client connected" , client_name); |
286 | |
287 | lldb::IOObjectSP io(std::move(sock)); |
288 | |
289 | // Move the client into a background thread to unblock accepting the next |
290 | // client. |
291 | std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex, |
292 | &dap_sessions]() { |
293 | llvm::set_thread_name(client_name + ".runloop" ); |
294 | Transport transport(client_name, log, io, io); |
295 | DAP dap(log, default_repl_mode, pre_init_commands, transport); |
296 | |
297 | if (auto Err = dap.ConfigureIO()) { |
298 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
299 | ErrorBanner: "Failed to configure stdout redirect: " ); |
300 | return; |
301 | } |
302 | |
303 | { |
304 | std::scoped_lock<std::mutex> lock(dap_sessions_mutex); |
305 | dap_sessions[io.get()] = &dap; |
306 | } |
307 | |
308 | if (auto Err = dap.Loop()) { |
309 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
310 | ErrorBanner: "DAP session (" + client_name + |
311 | ") error: " ); |
312 | } |
313 | |
314 | DAP_LOG(log, "({0}) client disconnected" , client_name); |
315 | std::unique_lock<std::mutex> lock(dap_sessions_mutex); |
316 | dap_sessions.erase(x: io.get()); |
317 | std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); |
318 | }); |
319 | client.detach(); |
320 | }); |
321 | |
322 | if (auto Err = handle.takeError()) { |
323 | return Err; |
324 | } |
325 | |
326 | status = g_loop.Run(); |
327 | if (status.Fail()) { |
328 | return status.takeError(); |
329 | } |
330 | |
331 | DAP_LOG( |
332 | log, |
333 | "lldb-dap server shutdown requested, disconnecting remaining clients..." ); |
334 | |
335 | bool client_failed = false; |
336 | { |
337 | std::scoped_lock<std::mutex> lock(dap_sessions_mutex); |
338 | for (auto [sock, dap] : dap_sessions) { |
339 | if (llvm::Error error = dap->Disconnect()) { |
340 | client_failed = true; |
341 | llvm::errs() << "DAP client " << dap->transport.GetClientName() |
342 | << " disconnected failed: " |
343 | << llvm::toString(E: std::move(error)) << "\n" ; |
344 | } |
345 | // Close the socket to ensure the DAP::Loop read finishes. |
346 | sock->Close(); |
347 | } |
348 | } |
349 | |
350 | // Wait for all clients to finish disconnecting. |
351 | std::unique_lock<std::mutex> lock(dap_sessions_mutex); |
352 | dap_sessions_condition.wait(lock&: lock, p: [&] { return dap_sessions.empty(); }); |
353 | |
354 | if (client_failed) |
355 | return llvm::make_error<llvm::StringError>( |
356 | Args: "disconnecting all clients failed" , Args: llvm::inconvertibleErrorCode()); |
357 | |
358 | return llvm::Error::success(); |
359 | } |
360 | |
361 | int main(int argc, char *argv[]) { |
362 | llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false); |
363 | #if !defined(__APPLE__) |
364 | llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL |
365 | " and include the crash backtrace.\n" ); |
366 | #else |
367 | llvm::setBugReportMsg("PLEASE submit a bug report to " LLDB_BUG_REPORT_URL |
368 | " and include the crash report from " |
369 | "~/Library/Logs/DiagnosticReports/.\n" ); |
370 | #endif |
371 | |
372 | llvm::SmallString<256> program_path(argv[0]); |
373 | llvm::sys::fs::make_absolute(path&: program_path); |
374 | DAP::debug_adapter_path = program_path; |
375 | |
376 | LLDBDAPOptTable T; |
377 | unsigned MAI, MAC; |
378 | llvm::ArrayRef<const char *> ArgsArr = llvm::ArrayRef(argv + 1, argc); |
379 | llvm::opt::InputArgList input_args = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC); |
380 | |
381 | if (input_args.hasArg(OPT_help)) { |
382 | PrintHelp(table&: T, tool_name: llvm::sys::path::filename(path: argv[0])); |
383 | return EXIT_SUCCESS; |
384 | } |
385 | |
386 | if (input_args.hasArg(OPT_version)) { |
387 | PrintVersion(); |
388 | return EXIT_SUCCESS; |
389 | } |
390 | |
391 | ReplMode default_repl_mode = ReplMode::Auto; |
392 | if (input_args.hasArg(OPT_repl_mode)) { |
393 | llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode); |
394 | llvm::StringRef repl_mode_value = repl_mode->getValue(); |
395 | if (repl_mode_value == "auto" ) { |
396 | default_repl_mode = ReplMode::Auto; |
397 | } else if (repl_mode_value == "variable" ) { |
398 | default_repl_mode = ReplMode::Variable; |
399 | } else if (repl_mode_value == "command" ) { |
400 | default_repl_mode = ReplMode::Command; |
401 | } else { |
402 | llvm::errs() << "'" << repl_mode_value |
403 | << "' is not a valid option, use 'variable', 'command' or " |
404 | "'auto'.\n" ; |
405 | return EXIT_FAILURE; |
406 | } |
407 | } |
408 | |
409 | if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) { |
410 | if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) { |
411 | lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; |
412 | llvm::opt::Arg *debugger_pid = input_args.getLastArg(OPT_debugger_pid); |
413 | if (debugger_pid) { |
414 | llvm::StringRef debugger_pid_value = debugger_pid->getValue(); |
415 | if (debugger_pid_value.getAsInteger(Radix: 10, Result&: pid)) { |
416 | llvm::errs() << "'" << debugger_pid_value |
417 | << "' is not a valid " |
418 | "PID\n" ; |
419 | return EXIT_FAILURE; |
420 | } |
421 | } |
422 | int target_args_pos = argc; |
423 | for (int i = 0; i < argc; i++) { |
424 | if (strcmp(s1: argv[i], s2: "--launch-target" ) == 0) { |
425 | target_args_pos = i + 1; |
426 | break; |
427 | } |
428 | } |
429 | if (llvm::Error err = |
430 | LaunchRunInTerminalTarget(target_arg&: *target_arg, comm_file: comm_file->getValue(), debugger_pid: pid, |
431 | argv: argv + target_args_pos)) { |
432 | llvm::errs() << llvm::toString(E: std::move(err)) << '\n'; |
433 | return EXIT_FAILURE; |
434 | } |
435 | } else { |
436 | llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be " |
437 | "specified\n" ; |
438 | return EXIT_FAILURE; |
439 | } |
440 | } |
441 | |
442 | std::string connection; |
443 | if (auto *arg = input_args.getLastArg(Ids: OPT_connection)) { |
444 | const auto *path = arg->getValue(); |
445 | connection.assign(s: path); |
446 | } |
447 | |
448 | #if !defined(_WIN32) |
449 | if (input_args.hasArg(OPT_wait_for_debugger)) { |
450 | printf(format: "Paused waiting for debugger to attach (pid = %i)...\n" , getpid()); |
451 | pause(); |
452 | } |
453 | #endif |
454 | |
455 | std::unique_ptr<Log> log = nullptr; |
456 | const char *log_file_path = getenv(name: "LLDBDAP_LOG" ); |
457 | if (log_file_path) { |
458 | std::error_code EC; |
459 | log = std::make_unique<Log>(args&: log_file_path, args&: EC); |
460 | if (EC) { |
461 | llvm::logAllUnhandledErrors(E: llvm::errorCodeToError(EC), OS&: llvm::errs(), |
462 | ErrorBanner: "Failed to create log file: " ); |
463 | return EXIT_FAILURE; |
464 | } |
465 | } |
466 | |
467 | // Initialize LLDB first before we do anything. |
468 | lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); |
469 | if (error.Fail()) { |
470 | lldb::SBStream os; |
471 | error.GetDescription(description&: os); |
472 | llvm::errs() << "lldb initialize failed: " << os.GetData() << "\n" ; |
473 | return EXIT_FAILURE; |
474 | } |
475 | |
476 | // Create a memory monitor. This can return nullptr if the host platform is |
477 | // not supported. |
478 | std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor = |
479 | lldb_private::MemoryMonitor::Create(callback: [log = log.get()]() { |
480 | DAP_LOG(log, "memory pressure detected" ); |
481 | lldb::SBDebugger::MemoryPressureDetected(); |
482 | }); |
483 | |
484 | if (memory_monitor) |
485 | memory_monitor->Start(); |
486 | |
487 | // Terminate the debugger before the C++ destructor chain kicks in. |
488 | auto terminate_debugger = llvm::make_scope_exit(F: [&] { |
489 | if (memory_monitor) |
490 | memory_monitor->Stop(); |
491 | lldb::SBDebugger::Terminate(); |
492 | }); |
493 | |
494 | std::vector<std::string> pre_init_commands; |
495 | for (const std::string &arg : |
496 | input_args.getAllArgValues(OPT_pre_init_command)) { |
497 | pre_init_commands.push_back(arg); |
498 | } |
499 | |
500 | if (!connection.empty()) { |
501 | auto maybeProtoclAndName = validateConnection(conn: connection); |
502 | if (auto Err = maybeProtoclAndName.takeError()) { |
503 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
504 | ErrorBanner: "Invalid connection: " ); |
505 | return EXIT_FAILURE; |
506 | } |
507 | |
508 | Socket::SocketProtocol protocol; |
509 | std::string name; |
510 | std::tie(args&: protocol, args&: name) = *maybeProtoclAndName; |
511 | if (auto Err = serveConnection(protocol, name, log: log.get(), default_repl_mode, |
512 | pre_init_commands)) { |
513 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
514 | ErrorBanner: "Connection failed: " ); |
515 | return EXIT_FAILURE; |
516 | } |
517 | |
518 | return EXIT_SUCCESS; |
519 | } |
520 | |
521 | #if defined(_WIN32) |
522 | // Windows opens stdout and stdin in text mode which converts \n to 13,10 |
523 | // while the value is just 10 on Darwin/Linux. Setting the file mode to |
524 | // binary fixes this. |
525 | int result = _setmode(fileno(stdout), _O_BINARY); |
526 | assert(result); |
527 | result = _setmode(fileno(stdin), _O_BINARY); |
528 | UNUSED_IF_ASSERT_DISABLED(result); |
529 | assert(result); |
530 | #endif |
531 | |
532 | int stdout_fd = DuplicateFileDescriptor(fd: fileno(stdout)); |
533 | if (stdout_fd == -1) { |
534 | llvm::logAllUnhandledErrors( |
535 | E: llvm::errorCodeToError(EC: llvm::errnoAsErrorCode()), OS&: llvm::errs(), |
536 | ErrorBanner: "Failed to configure stdout redirect: " ); |
537 | return EXIT_FAILURE; |
538 | } |
539 | |
540 | lldb::IOObjectSP input = std::make_shared<NativeFile>( |
541 | args: fileno(stdin), args: File::eOpenOptionReadOnly, args: NativeFile::Unowned); |
542 | lldb::IOObjectSP output = std::make_shared<NativeFile>( |
543 | args&: stdout_fd, args: File::eOpenOptionWriteOnly, args: NativeFile::Unowned); |
544 | |
545 | constexpr llvm::StringLiteral client_name = "stdio" ; |
546 | Transport transport(client_name, log.get(), input, output); |
547 | DAP dap(log.get(), default_repl_mode, pre_init_commands, transport); |
548 | |
549 | // stdout/stderr redirection to the IDE's console |
550 | if (auto Err = dap.ConfigureIO(stdout, stderr)) { |
551 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
552 | ErrorBanner: "Failed to configure stdout redirect: " ); |
553 | return EXIT_FAILURE; |
554 | } |
555 | |
556 | // used only by TestVSCode_redirection_to_console.py |
557 | if (getenv(name: "LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION" ) != nullptr) |
558 | redirection_test(); |
559 | |
560 | if (auto Err = dap.Loop()) { |
561 | DAP_LOG(log.get(), "({0}) DAP session error: {1}" , client_name, |
562 | llvm::toStringWithoutConsuming(Err)); |
563 | llvm::logAllUnhandledErrors(E: std::move(Err), OS&: llvm::errs(), |
564 | ErrorBanner: "DAP session error: " ); |
565 | return EXIT_FAILURE; |
566 | } |
567 | return EXIT_SUCCESS; |
568 | } |
569 | |